-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
559 lines (319 loc) · 263 KB
/
atom.xml
File metadata and controls
559 lines (319 loc) · 263 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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>ddrccw's library</title>
<link href="/atom.xml" rel="self"/>
<link href="https://ddrccw.github.io/"/>
<updated>2018-08-11T09:43:45.745Z</updated>
<id>https://ddrccw.github.io/</id>
<author>
<name>ddrccw</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>CocoaPods plugin开发</title>
<link href="https://ddrccw.github.io/2018/03/30/2018-03-30-develop-cocoapods-plugin/"/>
<id>https://ddrccw.github.io/2018/03/30/2018-03-30-develop-cocoapods-plugin/</id>
<published>2018-03-30T13:18:04.000Z</published>
<updated>2018-08-11T09:43:45.745Z</updated>
<content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>公司的项目构建使用的是CocoaPods工具,并且利用CocoaPods的插件机制对构建流程进行了深度定制。要了解公司项目的构建流程,势必要先了解一下Cocoapods的插件机制。刚好最近又要改一下同事写的插件,就搜了一下网上关于插件开发的资料,可惜网上的这块资料并不多,大多是插件的安装和使用教程。于是我就写了这篇文章来简单记录一下。当然,实际操作下来发现还是比较简单的。</p><h1 id="插件"><a href="#插件" class="headerlink" title="插件"></a>插件</h1><p><a href="https://github.com/CocoaPods/cocoapods-plugins" target="_blank" rel="noopener">cocoapods-plugins</a>是CocoaPods团队提供的一个用来获取插件信息列表的插件,同时它也提供利用插件模板工程构建插件项目和插件发布的功能。</p><p>插件加载原理:CocoaPods插件管理器会通过RubyGems查找文件列表包含cocoapods_plugin.rb文件的gem包并加载。</p><h2 id="插件工程创建和说明"><a href="#插件工程创建和说明" class="headerlink" title="插件工程创建和说明"></a>插件工程创建和说明</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pod plugins create NAME [TEMPLATE_URL]</span><br></pre></td></tr></table></figure><p>运行上面的这串命令,可以直接创建一个工程,工程结构如下:<br><figure class="highlight bash"><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><br><span class="line">├── Gemfile</span><br><span class="line">├── LICENSE.txt</span><br><span class="line">├── README.md</span><br><span class="line">├── Rakefile</span><br><span class="line">├── cocoapods-demoplugin.gemspec</span><br><span class="line">├── lib</span><br><span class="line">│ ├── cocoapods-demoplugin</span><br><span class="line">│ │ ├── <span class="built_in">command</span></span><br><span class="line">│ │ │ └── demoplugin.rb</span><br><span class="line">│ │ ├── command.rb</span><br><span class="line">│ │ └── gem_version.rb</span><br><span class="line">│ ├── cocoapods-demoplugin.rb</span><br><span class="line">│ └── cocoapods_plugin.rb</span><br><span class="line">└── spec</span><br><span class="line"> ├── <span class="built_in">command</span></span><br><span class="line"> │ └── demoplugin_spec.rb</span><br><span class="line"> └── spec_helper.rb</span><br></pre></td></tr></table></figure></p><p><code>Gemfile</code>:插件工程本身是用<a href="http://bundler.io/" target="_blank" rel="noopener">Bundler</a>来管理项目依赖</p><p><code>Rakefile</code>: <code>rake</code>默认执行spec目录下的spec文件执行用例自测</p><p><code>cocoapods-demoplugin.gemspec</code>: 发布gem包需要的描述文件</p><p><code>demoplugin.rb</code>: 插件实现文件</p><p><code>demoplugin_spec.rb</code>: 默认帮你实现插件注册,可以在里面写用例测试插件</p><p>注意:如果插件创建时,你传入的NAME参数里的值包含大写,那么<code>rake</code>执行spec会不通过,似乎是因为pod的插件命令只支持小写,你需要把<code>demoplugin_spec.rb</code>里命令解析传的参数值改成小写</p><h2 id="插件的开发和调试"><a href="#插件的开发和调试" class="headerlink" title="插件的开发和调试"></a>插件的开发和调试</h2><p>1 开发</p><p>如上所述,默认的插件实现文件是<code>demoplugin.rb</code></p><figure class="highlight ruby"><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">module</span> <span class="title">Pod</span></span></span><br><span class="line"> <span class="class"><span class="keyword">class</span> <span class="title">Command</span></span></span><br><span class="line"> <span class="comment"># This is an example of a cocoapods plugin adding a top-level subcommand</span></span><br><span class="line"> <span class="comment"># to the 'pod' command.</span></span><br><span class="line"> <span class="comment">#</span></span><br><span class="line"> <span class="comment"># You can also create subcommands of existing or new commands. Say you</span></span><br><span class="line"> <span class="comment"># wanted to add a subcommand to `list` to show newly deprecated pods,</span></span><br><span class="line"> <span class="comment"># (e.g. `pod list deprecated`), there are a few things that would need</span></span><br><span class="line"> <span class="comment"># to change.</span></span><br><span class="line"> <span class="comment">#</span></span><br><span class="line"> <span class="comment"># - move this file to `lib/pod/command/list/deprecated.rb` and update</span></span><br><span class="line"> <span class="comment"># the class to exist in the the Pod::Command::List namespace</span></span><br><span class="line"> <span class="comment"># - change this class to extend from `List` instead of `Command`. This</span></span><br><span class="line"> <span class="comment"># tells the plugin system that it is a subcommand of `list`.</span></span><br><span class="line"> <span class="comment"># - edit `lib/cocoapods_plugins.rb` to require this file</span></span><br><span class="line"> <span class="comment">#</span></span><br><span class="line"> <span class="comment"># <span class="doctag">@todo</span> Create a PR to add your plugin to CocoaPods/cocoapods.org</span></span><br><span class="line"> <span class="comment"># in the `plugins.json` file, once your plugin is released.</span></span><br><span class="line"> <span class="comment">#</span></span><br><span class="line"> <span class="class"><span class="keyword">class</span> <span class="title">Demoplugin</span> < Command</span></span><br><span class="line"> <span class="keyword">self</span>.summary = <span class="string">'Short description of cocoapods-demoplugin.'</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">self</span>.description = <span class="string"><<-DESC</span></span><br><span class="line"><span class="string"> Longer description of cocoapods-demoplugin.</span></span><br><span class="line"><span class="string"> DESC</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">self</span>.arguments = <span class="string">'NAME'</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">initialize</span><span class="params">(argv)</span></span></span><br><span class="line"> @name = argv.shift_argument</span><br><span class="line"> <span class="keyword">super</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">validate!</span></span></span><br><span class="line"> <span class="keyword">super</span></span><br><span class="line"> <span class="comment">#help! 'A Pod name is required.' unless <span class="doctag">@name</span></span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">run</span></span></span><br><span class="line"> UI.puts <span class="string">"Add your implementation for the cocoapods-demoplugin plugin in <span class="subst">#{__FILE_<span class="number">_</span>}</span>"</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>看了一下其中的代码,可以知道插件命令实际上继承自<a href="https://github.com/CocoaPods/CLAide" target="_blank" rel="noopener">CLAide</a>的Command类。类里面就实现了3个方法,包含插件命令执行过程中的3个环节:初始化,命令参数验证,插件执行。同时注释里还提示Command可以有自己的subCommand。<br>好吧,代码看上去就是这么简单。。</p><p>2 调试</p><p>基本流程就是在spec文件<code>demoplugin_spec.rb</code>里写自测逻辑,通过<code>rake</code>命令执行验证。<br>在插件里,你可以hook到CocoaPods的任意模块,再配合rubymine写代码,就能很方便地深入了解CocoaPods机制。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>CocoaPods插件只是CocoaPods构建环节中的冰山一角。如果你想深入定制一些功能,学会定制插件很有必要。当然,继续深入了解CocoaPods作为oc,swift工程的包依赖管理器的设计,也是很有意义的。</p>]]></content>
<summary type="html">
<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>公司的项目构建使用的是CocoaPods工具,并且利用CocoaPods的插件机制对构建流程进行了深度定制。要了解公司项目的构建流程,势必要
</summary>
<category term="iOS-dev" scheme="https://ddrccw.github.io/categories/iOS-dev/"/>
<category term="CocoaPods" scheme="https://ddrccw.github.io/tags/CocoaPods/"/>
<category term="plugin" scheme="https://ddrccw.github.io/tags/plugin/"/>
<category term="ruby" scheme="https://ddrccw.github.io/tags/ruby/"/>
</entry>
<entry>
<title>小试Xcode逆向:app内存监控原理初探</title>
<link href="https://ddrccw.github.io/2017/12/30/2017-12-30-reverse-xcode-with-lldb-and-hopper-disassembler/"/>
<id>https://ddrccw.github.io/2017/12/30/2017-12-30-reverse-xcode-with-lldb-and-hopper-disassembler/</id>
<published>2017-12-30T11:50:14.000Z</published>
<updated>2018-08-11T14:02:42.668Z</updated>
<content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>最近看到公司同事的《iOS内存那些事》系列文章,其中的一篇文章讲了他在研究WebKit中内存管理的时候,发现可以用phys_footprint来衡量内存,其结果和xcode debug显示的值基本一致。文章通读下来,收获颇丰~回味之余,突然脑洞了一下,为啥不直接逆向一下Xcode,学习一下xcode debug app时它是怎么实现内存监控的?刚好最近在自学逆向知识,顺便也来练练手~</p><h1 id="动手实践"><a href="#动手实践" class="headerlink" title="动手实践"></a>动手实践</h1><h3 id="准备一个小项目"><a href="#准备一个小项目" class="headerlink" title="准备一个小项目"></a>准备一个小项目</h3><p>运行一下,我们可以在debug面板看到memory report信息<br><img src="/images/reverse-xcode-with-lldb-and-hopper-disassembler/debug.png" alt="undefined">{:height=”100%” width=”100%”}</p><h3 id="lldb和hopper的使用"><a href="#lldb和hopper的使用" class="headerlink" title="lldb和hopper的使用"></a>lldb和hopper的使用</h3><ul><li>通过如下操作,我们可以直接attach Xcode调试</li></ul><figure class="highlight bash"><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">➜ ~ lldb -n Xcode</span><br><span class="line">(lldb) process attach --name <span class="string">"Xcode"</span></span><br><span class="line">Process 969 stopped</span><br><span class="line">* thread <span class="comment">#1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP</span></span><br><span class="line"> frame <span class="comment">#0: 0x00007fffe2bcb34a libsystem_kernel.dylib`mach_msg_trap + 10</span></span><br><span class="line">libsystem_kernel.dylib`mach_msg_trap:</span><br><span class="line">-> 0x7fffe2bcb34a <+10>: retq</span><br><span class="line"> 0x7fffe2bcb34b <+11>: nop</span><br><span class="line"></span><br><span class="line">libsystem_kernel.dylib`mach_msg_overwrite_trap:</span><br><span class="line"> 0x7fffe2bcb34c <+0>: movq %rcx, %r10</span><br><span class="line"> 0x7fffe2bcb34f <+3>: movl <span class="variable">$0x1000020</span>, %eax ; imm = 0x1000020</span><br><span class="line">Target 0: (Xcode) stopped.</span><br><span class="line"></span><br><span class="line">Executable module <span class="built_in">set</span> to <span class="string">"/Applications/Xcode.app/Contents/MacOS/Xcode"</span>.</span><br><span class="line">Architecture <span class="built_in">set</span> to: x86_64h-apple-macosx.</span><br><span class="line">(lldb) c</span><br><span class="line">Process 969 resuming</span><br><span class="line">(lldb)</span><br></pre></td></tr></table></figure><ul><li>来到Xcode debug面板,可以直接看到app运行时的内存信息。先小试一下那个内存信息栏能否响应点击操作。加个断点,尝试点击一下那个内存栏,bingo,顺利跑到断点处~</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">(lldb) b -[NSResponder mouseUp:]</span><br><span class="line">Breakpoint 1: <span class="built_in">where</span> = AppKit`-[NSResponder mouseUp:], address = 0x00007fffcb070177</span><br><span class="line">Process 969 stopped</span><br><span class="line">* thread <span class="comment">#1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1</span></span><br><span class="line"> frame <span class="comment">#0: 0x00007fffcb070177 AppKit`-[NSResponder mouseUp:]</span></span><br><span class="line">AppKit`-[NSResponder mouseUp:]:</span><br><span class="line">-> 0x7fffcb070177 <+0>: pushq %rbp</span><br><span class="line"> 0x7fffcb070178 <+1>: movq %rsp, %rbp</span><br><span class="line"> 0x7fffcb07017b <+4>: popq %rbp</span><br><span class="line"> 0x7fffcb07017c <+5>: jmp 0x7fffcaf94724 ; forwardMethod</span><br><span class="line">Target 0: (Xcode) stopped.(lldb)</span><br></pre></td></tr></table></figure><p>因为Xcode肯定是x86_64架构编译的,所以通过<code>po $rdi</code>,可以看到点击方法的对象是<code><NSTextField: 0x7fb7aed38280></code>。 第一直觉告诉我,NSTextField是不是类似于UITextField,有text属性可以被赋值?查看了一下apple文档,它的父类<code>NSControl</code>有个<code>stringValue</code>属性可以设置,设断点,发现面板上内存变化时,断点触发了,<code>bt</code>一下,可以看到如下信息(注意,要先确定触发断点的是展示内存的那个NSTextField)</p><figure class="highlight bash"><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">(lldb) bt</span><br><span class="line">* thread <span class="comment">#1, queue = 'MainQueue: -[DBGLLDBSession processProfileDataString:]_block_invoke', stop reason = breakpoint 1.1</span></span><br><span class="line"> * frame <span class="comment">#0: 0x00007fffcaef1897 AppKit`-[NSControl setStringValue:]</span></span><br><span class="line"> frame <span class="comment">#1: 0x0000000125f5b305 DebuggerUI`__54-[DBGGaugeMemoryEditor _setupTopSectionComponentViews]_block_invoke + 955</span></span><br><span class="line"> frame <span class="comment">#2: 0x0000000106e49e36 DVTFoundation`-[DVTObservingBlockToken observeValueForKeyPath:ofObject:change:context:] + 610</span></span><br><span class="line"> frame <span class="comment">#3: 0x00007fffced5035d Foundation`NSKeyValueNotifyObserver + 350</span></span><br><span class="line"> frame <span class="comment">#4: 0x00007fffced4fbf4 Foundation`NSKeyValueDidChange + 486</span></span><br><span class="line"> frame <span class="comment">#5: 0x00007fffcee8e867 Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:usingBlock:] + 944</span></span><br><span class="line"> frame <span class="comment">#6: 0x00007fffced1395d Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:] + 60</span></span><br><span class="line"> frame <span class="comment">#7: 0x00007fffced7c23b Foundation`_NSSetObjectValueAndNotify + 261</span></span><br><span class="line"> frame <span class="comment">#8: 0x0000000106e8a742 DVTFoundation`__DVTDispatchAsync_block_invoke + 97</span></span><br><span class="line"> frame <span class="comment">#9: 0x00007fffe2a77524 libdispatch.dylib`_dispatch_call_block_and_release + 12</span></span><br><span class="line"> frame <span class="comment">#10: 0x00007fffe2a6e8fc libdispatch.dylib`_dispatch_client_callout + 8</span></span><br><span class="line"> frame <span class="comment">#11: 0x00007fffe2a849a0 libdispatch.dylib`_dispatch_queue_serial_drain + 896</span></span><br><span class="line"> frame <span class="comment">#12: 0x00007fffe2a77306 libdispatch.dylib`_dispatch_queue_invoke + 1046</span></span><br><span class="line"> frame <span class="comment">#13: 0x00007fffe2a7b908 libdispatch.dylib`_dispatch_main_queue_callback_4CF + 505</span></span><br><span class="line"> frame <span class="comment">#14: 0x00007fffcd35bbc9 CoreFoundation`__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9</span></span><br><span class="line"> frame <span class="comment">#15: 0x00007fffcd31cc0d CoreFoundation`__CFRunLoopRun + 2205</span></span><br><span class="line"> frame <span class="comment">#16: 0x00007fffcd31c114 CoreFoundation`CFRunLoopRunSpecific + 420</span></span><br><span class="line"> frame <span class="comment">#17: 0x00007fffcc87cebc HIToolbox`RunCurrentEventLoopInMode + 240</span></span><br><span class="line"> frame <span class="comment">#18: 0x00007fffcc87ccf1 HIToolbox`ReceiveNextEventCommon + 432</span></span><br><span class="line"> frame <span class="comment">#19: 0x00007fffcc87cb26 HIToolbox`_BlockUntilNextEventMatchingListInModeWithFilter + 71</span></span><br><span class="line"> frame <span class="comment">#20: 0x00007fffcae15a54 AppKit`_DPSNextEvent + 1120</span></span><br><span class="line"> frame <span class="comment">#21: 0x00007fffcb5917ee AppKit`-[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 2796</span></span><br><span class="line"> frame <span class="comment">#22: 0x000000010743d98e DVTKit`-[DVTApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 390</span></span><br><span class="line"> frame <span class="comment">#23: 0x00007fffcae0a3db AppKit`-[NSApplication run] + 926</span></span><br><span class="line"> frame <span class="comment">#24: 0x00007fffcadd4e0e AppKit`NSApplicationMain + 1237</span></span><br><span class="line"> frame <span class="comment">#25: 0x00007fffe2aa4235 libdyld.dylib`start + 1</span></span><br><span class="line"> frame <span class="comment">#26: 0x00007fffe2aa4235 libdyld.dylib`start + 1</span></span><br></pre></td></tr></table></figure><p>从函数调用栈上,我们可以看出,NSTextField值的变化是通过kvo某个值实现的。<code>image lookup -rn '\[DBGGaugeMemoryEditor\</code>,可以发现它位于<code>/Applications/Xcode.app/Contents/PlugIns/DebuggerUI.ideplugin/Contents/MacOS/DebuggerUI</code>, 把二进制文件拖到hooper里看一下<code>-[DBGGaugeMemoryEditor _setupTopSectionComponentViews]_block_invoke</code>的实现。</p><p><img src="/images/reverse-xcode-with-lldb-and-hopper-disassembler/code.png" alt="undefined">{:height=”100%” width=”100%”}</p><p>通过查看相应的实现,可以知道是它通过debugSession实例获取相关信息的,再结合调用栈信息,debugSession肯定就是DBGLLDBSession。同样的,通过lldb,我们可以找到DBGLLDBSession位于<code>/Applications/Xcode.app/Contents/PlugIns/DebuggerLLDB.ideplugin/Contents/MacOS/DebuggerLLDB</code>,企图通过hooper看看它的实现,然而并没看出啥有用信息。只能继续尝试lldb断点。<code>po $rdx</code>打印它的参数,似乎出了一串奇怪的字符串?!</p><figure class="highlight shell"><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">(lldb) b -[DBGLLDBSession processProfileDataString:]</span><br><span class="line">Breakpoint 3: where = DebuggerLLDB`-[DBGLLDBSession processProfileDataString:], address = 0x0000000115f99c52</span><br><span class="line">(lldb) c</span><br><span class="line">Process 4489 resuming</span><br><span class="line">Process 4489 stopped</span><br><span class="line">* thread #30, name = '<DBGLLDBSessionThread (pid=51274)>', stop reason = breakpoint 3.1</span><br><span class="line"> frame #0: 0x0000000115f99c52 DebuggerLLDB`-[DBGLLDBSession processProfileDataString:]</span><br><span class="line">DebuggerLLDB`-[DBGLLDBSession processProfileDataString:]:</span><br><span class="line"><span class="meta">-></span><span class="bash"> 0x115f99c52 <+0>: push rbp</span></span><br><span class="line"> 0x115f99c53 <+1>: mov rbp, rsp</span><br><span class="line"> 0x115f99c56 <+4>: push r15</span><br><span class="line"> 0x115f99c58 <+6>: push r14</span><br><span class="line">Target 0: (Xcode) stopped.</span><br><span class="line">(lldb) po $rdi</span><br><span class="line"><DBGLLDBSession: 0x7f9c998bec90></span><br><span class="line"></span><br><span class="line">(lldb) po $rdx</span><br><span class="line">num_cpu:8;host_user_ticks:3332988;host_sys_ticks:2148237;host_idle_ticks:23546214;elapsed_usec:1513647973058229;task_used_usec:43128;thread_used_id:1;thread_used_usec:841463;thread_used_name:;thread_used_id:4;thread_used_usec:595;thread_used_name:;thread_used_id:5;thread_used_usec:2130;thread_used_name:576562546872656164;thread_used_id:6;thread_used_usec:3012;thread_used_name:636f6d2e6170706c652e75696b69742e6576656e7466657463682d746872656164;thread_used_id:11;thread_used_usec:255;thread_used_name:;total:17179869184;used:14274596864;rprvt:0;purgeable:0;anonymous:57823232;energy:98435210380;</span><br></pre></td></tr></table></figure><h3 id="google大法,lldb源码查看"><a href="#google大法,lldb源码查看" class="headerlink" title="google大法,lldb源码查看"></a>google大法,lldb源码查看</h3><p>随便抽了一个关键字<code>host_sys_ticks</code>,google了一下,发现这串字符,竟然来自lldb项目里的debugserver!先查看了一下本机的lldb版本(lldb-900.0.45),在apple open source的<a href="https://opensource.apple.com/tarballs/lldb/" target="_blank" rel="noopener">官网</a>上没找到这个版本的lldb。无奈下只能去<a href="https://lldb.llvm.org/source.html" target="_blank" rel="noopener">lldb官网</a>clone了一份最新的代码,虽然不知道apple是基于哪个lldb版本开发的,但是看最新的实现总不会错~</p><p>通过查看debugserver源码,可以发现前面那串字符串是在<code>std::string MachTask::GetProfileData(DNBProfileDataScanType scanType)</code>生成的,<a href="https://github.com/llvm-mirror/lldb/blob/master/tools/debugserver/source/MacOSX/MachTask.mm" target="_blank" rel="noopener">里面</a>有各种profile信息,比如cpu,memory等。</p><p><strong>大胆猜想一下,Xcode的内存监控正是定时通过获取debugserver的这个方法的信息来展示的!!!</strong></p><p>另外,关于debugserver,可以看<a href="http://iphonedevwiki.net/index.php/Debugserver" target="_blank" rel="noopener">这里</a>的介绍。简单来说,它是运行在ios上的一个可以接受lldb前端命令的『远程调试』服务器。在越狱设备上,可以通过它做很多trick,这里暂且不表。</p><h1 id="验证"><a href="#验证" class="headerlink" title="验证"></a>验证</h1><h3 id="初步"><a href="#初步" class="headerlink" title="初步"></a>初步</h3><p>扒出memory profile的<a href="https://github.com/llvm-mirror/lldb/blob/master/tools/debugserver/source/MacOSX/MachVMMemory.cpp" target="_blank" rel="noopener">代码实现</a>如下:</p><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><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="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">GetPurgeableAndAnonymous</span><span class="params">(<span class="keyword">task_t</span> task, <span class="keyword">uint64_t</span> &purgeable,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">uint64_t</span> &anonymous)</span> </span>{</span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> defined(TASK_VM_INFO) && TASK_VM_INFO >= 22</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">kern_return_t</span> kr;</span><br><span class="line"> <span class="keyword">mach_msg_type_number_t</span> info_count;</span><br><span class="line"> <span class="keyword">task_vm_info_data_t</span> vm_info;</span><br><span class="line"></span><br><span class="line"> info_count = TASK_VM_INFO_COUNT;</span><br><span class="line"> kr = task_info(task, TASK_VM_INFO_PURGEABLE, (<span class="keyword">task_info_t</span>)&vm_info,</span><br><span class="line"> &info_count);</span><br><span class="line"> <span class="keyword">if</span> (kr == KERN_SUCCESS) {</span><br><span class="line"> purgeable = vm_info.purgeable_volatile_resident;</span><br><span class="line"> anonymous =</span><br><span class="line"> vm_info.internal + vm_info.compressed - vm_info.purgeable_volatile_pmap;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>再看看前面获取的anonymous的字节值,57823232对应的正是55.1445007MB,即debug内存面板展示的值!</p><p>这里再附上同事发现的WebKit内存计算公式,可以比较一下理解,具体参看<a href="https://raw.githubusercontent.com/apple/darwin-xnu/master/osfmk/kern/task.c" target="_blank" rel="noopener">这里</a>,搜索一下<code>phys_footprint</code>便知。</p><p><code>phys_footprint = (internal - alternate_accounting) + (internal_compressed - alternate_accounting_compressed) + iokit_mapped + purgeable_nonvolatile + purgeable_nonvolatile_compressed + page_table</code></p><h3 id="具体"><a href="#具体" class="headerlink" title="具体"></a>具体</h3><p>本来按理说是应该直接把上述代码拷出来具体执行进一步确认一下。但是意外的找到了一个偷懒的方法。既然Xcode是通过debugserver获取到相关信息,那么有没有办法直接和debugserver交互来获取信息呢?</p><p>继续翻看了一下lldb代码,lldb前端确实就存在相应的命令来触发debugserver执行。</p><p>通过代码可以发现<code>std::string MachTask::GetProfileData(DNBProfileDataScanType scanType)</code>会在<code>RNBRemote</code>接受到消息包<code>qGetProfileData</code>时执行,而lldb原来可以直接通过<code>process plugin packet send</code>命令来给debugserver发送包命令。</p><p>换句话说,也就是直接在Xcode终端直接执行命令验证<br><img src="/images/reverse-xcode-with-lldb-and-hopper-disassembler/result.png" alt="undefined">{:height=”100%” width=”100%”} </p><p>结合lldb脚本的使用,目测验证起来并不难。</p><p>当然,最终可能还是要直接拷出一下那段代码验证一下,这个后面有空再试试。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ul><li>虽然咋看下来全文一路顺畅,但是作为一名逆向新手,中间还是遇到了不少问题,不过收获也是很大滴~</li><li>lldb和hopper确实很强大,深入学习一下lldb源码还是有必要的,其中还是有不少有趣的地方值得挖掘。</li><li>通过Xcode debug机制的原理探寻,我们可以学习它的profile实现并且自己撸一遍做一套性能监控.</li></ul><h1 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h1><p><a href="https://store.raywenderlich.com/products/advanced-apple-debugging-and-reverse-engineering" target="_blank" rel="noopener">https://store.raywenderlich.com/products/advanced-apple-debugging-and-reverse-engineering</a></p>]]></content>
<summary type="html">
<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>最近看到公司同事的《iOS内存那些事》系列文章,其中的一篇文章讲了他在研究WebKit中内存管理的时候,发现可以用phys_footprin
</summary>
<category term="iOS-dev" scheme="https://ddrccw.github.io/categories/iOS-dev/"/>
<category term="iOS" scheme="https://ddrccw.github.io/tags/iOS/"/>
<category term="lldb" scheme="https://ddrccw.github.io/tags/lldb/"/>
<category term="Xcode" scheme="https://ddrccw.github.io/tags/Xcode/"/>
<category term="hopper disassembler" scheme="https://ddrccw.github.io/tags/hopper-disassembler/"/>
</entry>
<entry>
<title>再谈iOS旋转视图开发</title>
<link href="https://ddrccw.github.io/2015/06/24/2015-06-24-tips-on-changing-orientation-2/"/>
<id>https://ddrccw.github.io/2015/06/24/2015-06-24-tips-on-changing-orientation-2/</id>
<published>2015-06-24T05:22:22.000Z</published>
<updated>2018-08-11T15:05:54.461Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>经过了<a href="https://itunes.apple.com/cn/app/wang-yi-yun-ke-tang-for-iphone/id880452926?mt=8" target="_blank" rel="noopener">云课堂</a>,<a href="https://itunes.apple.com/cn/app/id415424368?mt=8" target="_blank" rel="noopener">公开课</a>,<a href="https://itunes.apple.com/cn/app/id977883304" target="_blank" rel="noopener">中国大学mooc</a>三种旋转需求的考(zhe)验(mo),也踩过了好多坑。虽然之前有写过一篇<a href="https://ddrccw.github.io/2014/08/19/2014-08-19-tips-on-changing-orientation/">文章</a>已经小结过一些问题,但现在再读,感觉还探讨地不够,故又补上一篇,希望能就此彻底做个了结。当然,也希望读过此文的童鞋能少走些弯路。。</p><p>注:此文提到的旋转包括页面和状态栏</p><h2 id="正题"><a href="#正题" class="headerlink" title="正题"></a>正题</h2><h3 id="3种需求"><a href="#3种需求" class="headerlink" title="3种需求"></a>3种需求</h3><p>3个app的旋转需求实现难度可以说是从易到难。</p><h4 id="云课堂"><a href="#云课堂" class="headerlink" title="云课堂"></a>云课堂</h4><p>云课堂的需求和实现最简单。相信大部分的app都是这个需求。</p><p>需求:进入播放页面默认横屏,同时支持页面自动旋转。</p><p>实现:因为是基于设备的系统事件通知来让页面响应旋转,所以关键是只要设置好<code>- (BOOL)shouldAutorotate</code>、<code>- (NSUInteger)supportedInterfaceOrientations</code>、<code>- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation</code>等几个函数即可。</p><h4 id="公开课"><a href="#公开课" class="headerlink" title="公开课"></a>公开课</h4><p>公开课的需求和实现较云课堂来说要求要高一些。</p><p>需求:旋转的控制交给用户选择,而不是受系统控制。</p><p>实现:属于要无视设备的系统事件通知强制让页面旋转,具体可以参看<a href="https://ddrccw.github.io/2014/08/19/2014-08-19-tips-on-changing-orientation/">前文</a></p><h4 id="中国大学mooc"><a href="#中国大学mooc" class="headerlink" title="中国大学mooc"></a>中国大学mooc</h4><p>中国大学mooc的要求最高,是上述两种情况的结合体</p><p>需求:</p><ol><li><p>进入播放页面默认横屏,同时支持页面自动旋转;</p></li><li><p>页面旋转不受系统自带的竖排锁定按钮控制,但是用户可以锁定旋转方向。</p></li></ol><p>实现:</p><p>本质上来讲,它的实现是公开课方案的加强版。同样要无视设备的系统事件,而且是基于自定义的设备事件通知页面强制旋转。</p><p>强制旋转方式同公开课,但是实际开发过程中未遇到<a href="https://ddrccw.github.io/2014/08/19/2014-08-19-tips-on-changing-orientation/">前文</a>提到的需要强制把transform和frame的动画拆到两个animate block的问题,囧rz。。。关于自定义设备旋转通知,利用core motion来检测计算,具体代码是基于网上找的某段代码改的(抱歉忘了原作者是谁。。),代码查看请猛击<a href="https://github.com/ddrccw/CCRotation/blob/master/CCRotation/CCRotation/UIViewController%2BCCRotation.m" target="_blank" rel="noopener">这里</a>。</p><h3 id="一个坑引发的系列惨案"><a href="#一个坑引发的系列惨案" class="headerlink" title="一个坑引发的系列惨案"></a>一个坑引发的系列惨案</h3><p>前面概述了我经历过的一些开发需求,下面重点讲一下我在开发过程中遇到的一个坑。</p><h4 id="坑"><a href="#坑" class="headerlink" title="坑"></a>坑</h4><p>相信开发过强制旋转的童鞋,应该都遇到过这么一个令人抓狂的问题:</p><p>原本在iOS8之前都是运行正常的代码,在iOS 8上运行强制旋转过后,屏幕的部分区域不能响应你的touch(没错,就是有些区域的按钮不能点了。。)。情况如图(别人画的):</p><p><img src="/images/tips-on-changing-orientation-2/problem.png" alt="alt normal" title="fuck"></p><p>在stackoverflow上一搜,也可以搜出一堆,比如<a href="http://stackoverflow.com/questions/28684976/ios8-how-to-show-a-different-view-controller-when-the-phone-rotates-to-landscap?lq=1" target="_blank" rel="noopener">这个</a>,<a href="http://stackoverflow.com/questions/26037472/uiwindow-with-wrong-size-when-using-landscape-orientation" target="_blank" rel="noopener">这个</a>,还有<a href="http://stackoverflow.com/questions/25963101/unexpected-nil-window-in-uiapplicationhandleeventfromqueueevent" target="_blank" rel="noopener">这个</a>。</p><p>一度也怀疑是否是自己的代码哪里写的不对。直到官方贴出iOS 8.3的这个<a href="https://support.apple.com/kb/DL1806?locale=zh_CN" target="_blank" rel="noopener">升级说明</a>,基本可以确信这个是iOS 8.3以前的系统bug(iOS 8 bug好多?!也难怪iOS 9要专注于系统稳定性和优化)</p><p>那么,直接用iOS 8以前的代码,在iOS8.3之后的系统上是不是直接运行也就没有问题了呢?答案是否定的。因为iOS 8以前,window的frame和StatusBarOrientation无关,始终都是Portrait的,但是iOS 8以后window的frame和StatusBarOrientation有关,会随着StatusBarOrientation的变化而变化。如果你用到了window的frame,那么必然还是会有影响的。</p><p>这里我做了一个<a href="https://github.com/ddrccw/testRotate" target="_blank" rel="noopener">demo</a>,大家可以在iOS(-∞,8), [8, 8.3), [8.3, +∞)这三种情况的系统上运行一下,体会一下这个坑。。。</p><h4 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h4><p>既然知道坑的存在,那么又该如何解决呢?</p><ol><li><p>A模式(window-viewController-view)</p><p> 一般来说,待旋转的页面往往在window(无论是默认的window,还是你自己创建的window)下某个viewController内,而上述的bug也是发生在这种情况下。</p></li><li><p>B模式(window-view)</p><p> 偶然的一次搜索让我看到这个<a href="http://forum.unity3d.com/threads/problem-with-changing-screen-orientation.274056/" target="_blank" rel="noopener">帖子</a>,有人提到直接把待旋转的页面<code>addSubview</code>到window中(注:不要设置rootViewController),可以避免出现上述的bug。我在<a href="https://github.com/ddrccw/testRotate" target="_blank" rel="noopener">demo</a>中试了一下确实如此,然而遗憾的是,该方案反而会在[8.3, +∞)的系统上出现点击区域不全的问题。。。</p></li></ol><p>总之,我表示目前我还找不到一个简单的通用方法。。以下是我觉得设计代码时,可以考虑的思路</p><p><img src="/images/tips-on-changing-orientation-2/mind.png" alt="alt normal" title="mind"></p><p><strong>也就是说,保证强制旋转可行性的问题,是在保证全部区域可点的前提下,同时考虑是否要需要旋转状态栏的问题。要根据实际需求,针对不同的系统版本写兼容性代码。</strong></p><h4 id="连锁反应"><a href="#连锁反应" class="headerlink" title="连锁反应"></a>连锁反应</h4><ol><li><code>- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation</code>的影响。A模式下旋转页面和状态栏。如果是整个viewController被present,那么它在dimiss前,要保证状态栏的方向和<code>- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation</code>一致,否则在[8, 8.3)会发现dismiss后presentingViewController的方向会受影响。可以在<a href="https://github.com/ddrccw/testRotate" target="_blank" rel="noopener">demo</a>试试哦+_+</li><li>alert view的展示和状态栏的方向有关。如果你仅需要旋转页面而不转状态栏只是隐藏,那么在A模式下旋转了页面后,alert view的展示会是个问题。</li><li>如果alert view是定制的,那么请同时考虑1,2问题所带来的影响。对于前者,取决于你的alert view是否需要支持<code>preferredInterfaceOrientationForPresentation</code>;对于后者,你要让你的alert view判断好它所处的当前viewController的真实展示方向,不能简单通过状态栏判断哦。</li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>要想获得旋转问题(尤其是强制旋转)的完美解决,目前我觉得需要一定的开发成本,所以应该需要根据需求做出一点取舍吧^_^</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><p>1) <a href="http://stackoverflow.com/questions/28684976/ios8-how-to-show-a-different-view-controller-when-the-phone-rotates-to-landscap?lq=1" target="_blank" rel="noopener">http://stackoverflow.com/questions/28684976/ios8-how-to-show-a-different-view-controller-when-the-phone-rotates-to-landscap?lq=1</a> </p><p>2) <a href="http://forum.unity3d.com/threads/problem-with-changing-screen-orientation.274056/" target="_blank" rel="noopener">http://forum.unity3d.com/threads/problem-with-changing-screen-orientation.274056/</a></p>]]></content>
<summary type="html">
云课堂,公开课,中国大学mooc播放学习页面三种旋转方式的实现讨论
</summary>
<category term="iOS-dev" scheme="https://ddrccw.github.io/categories/iOS-dev/"/>
<category term="iOS" scheme="https://ddrccw.github.io/tags/iOS/"/>
<category term="orientation" scheme="https://ddrccw.github.io/tags/orientation/"/>
<category term="rotation" scheme="https://ddrccw.github.io/tags/rotation/"/>
</entry>
<entry>
<title>播放器旋转问题小结</title>
<link href="https://ddrccw.github.io/2014/08/19/2014-08-19-tips-on-changing-orientation/"/>
<id>https://ddrccw.github.io/2014/08/19/2014-08-19-tips-on-changing-orientation/</id>
<published>2014-08-19T15:33:07.000Z</published>
<updated>2018-08-11T15:06:48.259Z</updated>
<content type="html"><![CDATA[<h2 id="正题"><a href="#正题" class="headerlink" title="正题"></a>正题</h2><h3 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h3><p>新版公开课app的iphone版只支持Portrait,但是现在的需求是要求课程详情页面里嵌着的播放器支持从小屏幕旋90度并变为全屏。</p><h3 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h3><p>这是一个典型的旋转相关的问题。根据具体的情况,一般可以分成两类:</p><ol><li><p>基于设备的系统事件通知来让页面响应旋转</p><p> 关于这一点,以前写过一篇<a href="https://ddrccw.github.io/2012/12/02/2012-12-02-upgrade-to-ios6/">文章</a>其实已经提到过了。关键就是对<code>top-most full-screen</code>VC的理解,这里也就不赘述了。</p></li><li><p>无视设备的系统事件通知强制让页面旋转</p><p> 简单粗暴的描述一下就是即便通过设置开启竖屏锁定,也可以通过技术手段任意控制页面布局,而不受设备旋转事件的影响。</p></li></ol><p>回到前面新版公开课描述的需求,其实也就是一个要求强制旋转的问题。再进一步细化一下要解决的问题,包括有两个方面:</p><ol><li><p>状态栏旋转</p><p> 参考《<a href="https://developer.apple.com/LIBRARY/ios/releasenotes/General/RN-iOSSDK-6_0/index.html" title="RN-iOSSDK-6_0" target="_blank" rel="noopener">RN-iOSSDK-6_0</a>》,里面有这么一段描述:</p><blockquote><p>The <code>setStatusBarOrientation:animated:</code> method is not deprecated outright. It now works only if the <code>supportedInterfaceOrientations</code> method of the top-most full-screen view controller returns 0. This makes the caller responsible for ensuring that the status bar orientation is consistent.</p></blockquote><p> 文中介绍到的<code>setStatusBarOrientation:animated:</code>方法,正是用来处理状态栏强制旋转滴~</p></li><li><p>播放界面旋转并变为全屏</p><p> 通读一下《[IOS Orientation, 想怎么转就怎么转~~~][2]》和《[iOS旋转视图实践][3]》,基本可以确定需要通过UIView的transform来控制页面旋转方向。至于从小屏变化为全屏,显然是通过设置UIView的frame来实现。</p></li></ol><p>另外需要考虑的是要保证上述两方面的变化动画时间的一致。因为UIApplication的<code>statusBarOrientationAnimationDuration</code>是只读的,因此实际的动画时间也需要以它为基准。</p><h3 id="处理"><a href="#处理" class="headerlink" title="处理"></a>处理</h3><p>经过前面的分析,解决思路也很清晰,强制旋转问题确实属于一个老生常谈的小问题。。。但是,有些时候不得不感叹“纸上学来终觉浅”,具体处理过程中如果不小心又会掉进奇怪的坑里呢。。</p><h4 id="状态栏旋转"><a href="#状态栏旋转" class="headerlink" title="状态栏旋转"></a>状态栏旋转</h4><p>尽管《<a href="https://developer.apple.com/LIBRARY/ios/releasenotes/General/RN-iOSSDK-6_0/index.html" title="RN-iOSSDK-6_0" target="_blank" rel="noopener">RN-iOSSDK-6_0</a>》里面提到使用<code>setStatusBarOrientation:animated:</code>的前提之一是<code>supportedInterfaceOrientations</code>要返回0,但是根据实际的实验,其实只要<code>shouldAutorotate</code>返回NO即可。</p><h4 id="播放器旋转并变为全屏"><a href="#播放器旋转并变为全屏" class="headerlink" title="播放器旋转并变为全屏"></a>播放器旋转并变为全屏</h4><p>首先,看下接手公开课代码时看到的第一版代码,内容基本如下:</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></pre></td><td class="code"><pre><span class="line">[<span class="built_in">UIView</span> animateWithDuration:duration animations:^{</span><br><span class="line"><span class="built_in">CGSize</span> size = [<span class="built_in">UIScreen</span> mainScreen].bounds.size;</span><br><span class="line"> playerView_.frame = <span class="built_in">CGRectMake</span>(<span class="number">0</span>, <span class="number">0</span>, size.height, size.width);</span><br><span class="line"> <span class="keyword">self</span>.navigationController.view.transform = <span class="built_in">CGAffineTransformMakeRotation</span>(M_PI_2);</span><br><span class="line"> <span class="built_in">CGRect</span> frame = <span class="keyword">self</span>.navigationController.view.bounds;</span><br><span class="line"> <span class="keyword">self</span>.navigationController.view.frame = <span class="built_in">CGRectMake</span>(frame.origin.x, frame.origin.y, frame.size.height, frame.size.width);</span><br><span class="line">} completion:<span class="literal">nil</span>];</span><br></pre></td></tr></table></figure><p>照前面的分析来说,这段代码似乎没有问题,而且正常流程下从点击课程进入课程详情页里操作播放器旋转并使之变为全屏也没出现问题,但是坑爹的是,当navigationController被present后,再操作播放器,结果竟然是navigationController转了90度,但是播放器的frame动画却没有起作用了。</p><p>由于第一版代码先入为主的影响,刚开始还为是transform作用的view不对,我又一级一级从UIWindow一直到playerView_的各层view做了transform的尝试,当然问题依然存在。。。后来过了一宿,冷静思考了一下(果然还是不能太钻牛角尖啊= =),隐约觉得transform和frame的动画相互影响了,所有也就有了下述代码:</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></pre></td><td class="code"><pre><span class="line">[<span class="built_in">UIView</span> animateWithDuration:<span class="number">0</span> animations:^{</span><br><span class="line"><span class="keyword">self</span>.playerContainerView.transform = <span class="built_in">CGAffineTransformMakeRotation</span>(M_PI_2);</span><br><span class="line">} completion:^(<span class="built_in">BOOL</span> finished) {</span><br><span class="line">[<span class="built_in">UIView</span> animateWithDuration:duration animations:^{</span><br><span class="line"><span class="keyword">self</span>.playerContainerView.frame = <span class="keyword">self</span>.navigationController.view.bounds;</span><br><span class="line">}];</span><br><span class="line">}];</span><br></pre></td></tr></table></figure><p>上述代码解决了前面出现的问题,它和第一版的区别有:</p><ol><li><p>将transform和frame的动画拆到两个animate block中,这个也是最最关键的地方,虽然是看似很不起眼的一个处理,但是这里特别提一下,免得像我一样掉坑里浪费了时间。。</p></li><li><p>仅对要旋转的view做transform,这里考虑的是影响最小化的问题。如果是对它的superview做transform,显然会让一些不需要旋转的view重复做layout,从而带来一些潜在的影响。另外,还有一个小注意点,因为transform的是要旋转的view,所以设置它的frame时,它的坐标系仍是以它的superview为基准的,而不是像第一版代码那种要对换height和width的值。</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>总的来说问题不难,但是确实也让我找到一些思维痛点。看文章理解和实践起来确实是两码事,还是要多多coding啊!</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><p>1) <a href="https://developer.apple.com/LIBRARY/ios/releasenotes/General/RN-iOSSDK-6_0/index.html" title="RN-iOSSDK-6_0" target="_blank" rel="noopener">RN-iOSSDK-6_0</a> </p><p>2) [IOS Orientation, 想怎么转就怎么转~~~][2]</p><p>3) [iOS旋转视图实践][3]</p><p>[2]: <a href="http://www.cnblogs.com/jhzhu/p/3480885.html" target="_blank" rel="noopener">http://www.cnblogs.com/jhzhu/p/3480885.html</a> “IOS Orientation, 想怎么转就怎么转~~~”</p><p>[3]: <a href="http://rdc.taobao.org/?p=408" target="_blank" rel="noopener">http://rdc.taobao.org/?p=408</a> “iOS旋转视图实践”</p>]]></content>
<summary type="html">
tips on changing orientation
</summary>
<category term="iOS-dev" scheme="https://ddrccw.github.io/categories/iOS-dev/"/>
<category term="orientation" scheme="https://ddrccw.github.io/tags/orientation/"/>
</entry>
<entry>
<title>浅析MagicalRecord</title>
<link href="https://ddrccw.github.io/2014/05/18/2014-05-18-a-brief-analysis-and-tips-on-magialrecord/"/>
<id>https://ddrccw.github.io/2014/05/18/2014-05-18-a-brief-analysis-and-tips-on-magialrecord/</id>
<published>2014-05-18T08:16:53.000Z</published>
<updated>2018-08-11T14:02:42.659Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>从云课堂ipad版本的初步引入到iphone版本的全面使用,不得不说<a href="https://github.com/magicalpanda/MagicalRecord" title="MagicalRecord" target="_blank" rel="noopener">MagicalRecord</a>是一个十分有用的可以让开发人员快速上手复杂core data技术的开源库。趁着iphone版app审核,我也刚好抽空总结一下自己对<a href="https://github.com/magicalpanda/MagicalRecord" title="MagicalRecord" target="_blank" rel="noopener">MagicalRecord</a>的认识。</p><h2 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h2><p><a href="https://github.com/magicalpanda/MagicalRecord" title="MagicalRecord" target="_blank" rel="noopener">MagicalRecord</a>基于Core Data框架,提供了一系列category方法和类能很方便地操作持久层数据。关于它的使用,官网上有例子,而且也很简单,这里不赘述,具体可以参看《<a href="http://yannickloriot.com/2012/03/magicalrecord-how-to-make-programming-with-core-data-pleasant/#sthash.n5uCiXVB.dpbs" title="Magical Record: how to make programming with Core Data pleasant" target="_blank" rel="noopener">Magical Record: how to make programming with Core Data pleasant</a>》和《<a href="http://www.raywenderlich.com/56879/magicalrecord-tutorial-ios" title="magicalrecord-tutorial-ios" target="_blank" rel="noopener">MagicalRecord Tutorial for iOS</a>》。因为开发中的<a href="https://github.com/magicalpanda/MagicalRecord" title="MagicalRecord" target="_blank" rel="noopener">MagicalRecord</a>3.0会有比较大的变化,这里先说明一下,下文的相关阐述均基于2.2的版本。</p><h3 id="基本功能"><a href="#基本功能" class="headerlink" title="基本功能"></a>基本功能</h3><p>从目录结构上看,简单的分有core和Categories两块。</p><ul><li>categories里面主要是对core data中<code>NSManagedObject,NSManagedObjectContext、NSManagedObjectModel、NSPersistentStore和NSPersistentStoreCoordinator</code>对象的方法扩展。</li><li>core里面则是定义了一个MagicalRecord类,更多的从<a href="https://developer.apple.com/library/ios/documentation/DataManagement/Devpedia-CoreData/coreDataStack.html" title="coreDataStack" target="_blank" rel="noopener">core data stack</a>的角度,利用categories里的一些方法,全局性的管理。</li></ul><p>从使用core data最最基本的过程上看,一般我们是先创建一个<a href="https://developer.apple.com/library/ios/documentation/DataManagement/Devpedia-CoreData/coreDataStack.html" title="coreDataStack" target="_blank" rel="noopener">core data stack</a>,然后在不同的context中,对数据对象增删改查。</p><ul><li><p>Categories模块相对比较底层,主要负责对context对象,以及context中的对象的维护。另外,还有一个特殊的模块是关于数据导入,一般是在处理完json数据获得NSDictionary和NSArray后,希望快速的将数据转成<code>NSManagedObject</code>对象时使用。具体使用可以参考《<a href="http://www.cimgf.com/2012/05/29/importing-data-made-easy/" title="IMPORTING DATA MADE EASY" target="_blank" rel="noopener">IMPORTING DATA MADE EASY</a>》。</p><p> 实际使用过程中,由于后台数据结构定义的比较灵活,我也只是简单的使用其中的数据映射功能,即mappedKeyName的使用。</p></li><li><p>Core模块则是主要负责<a href="https://developer.apple.com/library/ios/documentation/DataManagement/Devpedia-CoreData/coreDataStack.html" title="coreDataStack" target="_blank" rel="noopener">core data stack</a>的创建和销毁(包括model version的merge),以及save操作的管理(其实save本质上是context控制的,但是为什么要设计在core模块中呢,这点会具体在后续的文字中体现)。</p></li></ul><h3 id="设计思想"><a href="#设计思想" class="headerlink" title="设计思想"></a>设计思想</h3><h4 id="core-data-stack"><a href="#core-data-stack" class="headerlink" title="core data stack"></a>core data stack</h4><p>从<code>MagicalRecord+Setup.h</code>提供的几个setup方法依次层层深入,基本可以确定<a href="https://github.com/magicalpanda/MagicalRecord" title="MagicalRecord" target="_blank" rel="noopener">MagicalRecord</a>中建议使用的<a href="https://developer.apple.com/library/ios/documentation/DataManagement/Devpedia-CoreData/coreDataStack.html" title="coreDataStack" target="_blank" rel="noopener">core data stack</a>模型是core data官方文档中介绍的最基本的模型,如下图</p><p><img src="/images/a-brief-analysis-and-tips-on-magialrecord/coreDataStack.png" alt="alt coreDataStack" title="coreDataStack"></p><p>即单个Persistent Object Store只对应单个Persistent Store Coordinator的模型。当然,这个只是它预先提供的一个实现。如果利用好categories模块的一些方法,我们还可以构建更加灵活的模型,这点会在后面谈扩展性的时候再说明。</p><h4 id="context的管理"><a href="#context的管理" class="headerlink" title="context的管理"></a>context的管理</h4><p>context的管理我觉得是最最核心的部分,因为它一方面涉及了性能上的问题,另一方面也涉及了如何保证不同context中数据一致性的问题。</p><p><strong><em>多个context的意义及常规设计</em></strong></p><p>虽然官例<a href="https://developer.apple.com/library/ios/samplecode/CoreDataBooks/Introduction/Intro.html" target="_blank" rel="noopener">CoreDataBooks</a>采用的是单个context的设计,但是这个设计显然并未充分发挥core data的强大功能,不足以满足复杂app的数据操作需求。一般常见的情况是,我们在增删改查数据的同时,需要尽可能的减少对UI的block。</p><p>关于这多个context的设计模式,我强烈建议读一下这两篇文章《<a href="http://www.cocoanetics.com/2012/07/multi-context-coredata/" title="Multi-Context CoreData" target="_blank" rel="noopener">Multi-Context CoreData</a>》和《<a href="http://www.cocoanetics.com/2013/02/zarra-on-locking/" title="Zarra on Locking" target="_blank" rel="noopener">Zarra on Locking</a>》。这里引用一下其中提到的三种设计模式,如下图。</p><ol><li><p>The “Traditional” Multi-Context Approach</p><p> <img src="/images/a-brief-analysis-and-tips-on-magialrecord/Traditional.png" alt="alt “Traditional”" title="Traditional"></p></li><li><p>Parent/Child Contexts</p><p> <img src="/images/a-brief-analysis-and-tips-on-magialrecord/Parent-Child.png" alt="alt “Parent-Child”" title="Parent-Child"></p></li><li><p>Asynchronous Saving</p><p> <img src="/images/a-brief-analysis-and-tips-on-magialrecord/Asynchronous-Saving.png" alt="alt “Asynchronous-Saving”" title="Asynchronous-Saving"></p></li></ol><p>再结合《<a href="http://floriankugler.com/blog/2013/4/29/concurrent-core-data-stack-performance-shootout" title="Concurrent Core Data Stacks – Performance Shootout" target="_blank" rel="noopener">Concurrent Core Data Stacks – Performance Shootout</a>》(如果对更底层的细节分析感兴趣,可以继续看《<a href="http://floriankugler.com/blog/2013/5/11/backstage-with-nested-managed-object-contexts" title="Backstage with Nested Managed Object Contexts" target="_blank" rel="noopener">Backstage with Nested Managed Object Contexts</a>》)一文分析,基本可以确信前面提到的三种设计中,2的设计不太建议。1的设计可以兼容ios3,性能较快,但是未用到core data在iOS 5引入的新特性(Parent/Child Contexts),维护多个context相对比较麻烦。3的设计使得维护多个context更简单,但带来的是性能上的部分牺牲。</p><p><strong><em>MagicalRecord的实现</em></strong></p><p>前面说了一堆常见的多个context的设计模式,再来看看<a href="https://github.com/magicalpanda/MagicalRecord" title="MagicalRecord" target="_blank" rel="noopener">MagicalRecord</a>又是如何设计的呢?</p><p>使用<a href="https://github.com/magicalpanda/MagicalRecord" title="MagicalRecord" target="_blank" rel="noopener">MagicalRecord</a>的过程中必然会涉及这么几个context:</p><ul><li>rootSavingContext</li><li>defaultContext</li><li>基于上述两种context,会频繁创建的child context</li></ul><p>其中除了defaultContext是<code>NSMainQueueConcurrencyType</code>,其他的context均是<code>NSPrivateQueueConcurrencyType</code></p><p>简单用图表示如下:</p><p><img src="/images/a-brief-analysis-and-tips-on-magialrecord/magicalRecord-context.png" alt="alt “magicalRecord-context”" title="magicalRecord-context"></p><p>注:连接线均表示Parent-Child的关系。</p><p>单看左边一条线,是不是很像前面提到设计模式3–Asynchronous Saving(好吧,其实就是它= =)。再看右边的child moc和左边的defaultContext的关系,咋看之下和设计模式1大不相同,但深入代码一看,还是有着某种共通之处。不过两个moc的通信,不单单通过直接的notification,而是借助rootSavingContext做了个中转。其中child moc通过save,将数据同步给rootSavingContext,而defaultContext则通过observer的方式,在rootSavingContext发生变化后做merge保证同步。这部分的设计思想,在<code>NSManagedObjectContext+MagicalRecord.m</code>文件中得到体现。</p><p>同样地,上述设计模式仅是<a href="https://github.com/magicalpanda/MagicalRecord" title="MagicalRecord" target="_blank" rel="noopener">MagicalRecord</a>的默认模式。如果你有自己的想法,完全可以利用<a href="https://github.com/magicalpanda/MagicalRecord" title="MagicalRecord" target="_blank" rel="noopener">MagicalRecord</a>提供的各种categories方法,设计适合自己的模式。</p><p><strong><em>多线程下context的管理</em></strong></p><p>众所周知,core data中NSPersistentStoreCoordinator的访问不是线程安全的。那么,简单的基于不同线程,然后创建相应的context是可行的吗?</p><p>看了一下源码<code>NSManagedObjectContext+MagicalThreading.m</code>,<a href="https://github.com/magicalpanda/MagicalRecord" title="MagicalRecord" target="_blank" rel="noopener">MagicalRecord</a>也确实是这样设计的,只不过在main thread时,它会直接使用defaultContext;而非main thread下,会根据映射表判断是否需要基于defaultContext创建child context,也就是上图中的左边那层关系。但是这种设计其实存在着一些隐患。尤其对于刚接触<a href="https://github.com/magicalpanda/MagicalRecord" title="MagicalRecord" target="_blank" rel="noopener">MagicalRecord</a>的开发人员,很容易忘乎所以的使用categories里提供的各种不用关心context创建的(即基于thread创建context)方法对数据增删查改。为啥说成隐患,因为有很大一部分情况下,操作数据是在UI展示那里处理的,也就是main thread的情况下,频繁的用那些api在defaultContext中save数据极有可能block UI(还记得defaultContext是<code>NSMainQueueConcurrencyType</code>么)。</p><p>还好<a href="https://github.com/magicalpanda/MagicalRecord" title="MagicalRecord" target="_blank" rel="noopener">MagicalRecord</a>针对常规的情况提供了另外一条save数据的途径,也就是<code>MagicalRecord+Actions.m</code>里提供的一些非基于当前线程创建context的方法。其本质上也就是上图中右边的那条路线。</p><p>关于这块设计的利弊,可以看<a href="https://github.com/magicalpanda/MagicalRecord" title="MagicalRecord" target="_blank" rel="noopener">MagicalRecord</a>作者自己的<a href="http://saulmora.com/2013/09/15/why-contextforcurrentthread-doesn-t-work-in-magicalrecord/" target="_blank" rel="noopener">观点</a>。另外,<a href="https://github.com/magicalpanda/MagicalRecord/wiki/Upgrading-to-MagicalRecord-3.0" target="_blank" rel="noopener">MagicalRecord 3.0</a>里,save的设计也发生了变更,和2.x之前版本比较,估计会有较大的不同。</p><p>建议:对应count,find操作直接使用defaultContext应该不会太大影响性能,但是一旦你要做save操作,请务必使用<code>MagicalRecord+Actions.m</code>里提供的那些非基于当前线程创建context的save方法。</p><h3 id="限制与扩展性"><a href="#限制与扩展性" class="headerlink" title="限制与扩展性"></a>限制与扩展性</h3><p>这里所谓的限制是指你直接使用默认的实现而不做任何额外的定制。这方面,个人觉得<a href="https://developer.apple.com/library/ios/documentation/DataManagement/Devpedia-CoreData/coreDataStack.html" title="coreDataStack" target="_blank" rel="noopener">core data stack</a>的默认实现虽然满足基本需求,但对于某些项目来说,可能还存在多个sqlite或一个PersistentStoreCoordinator同时有多个不同的PersistentStore等这样一些更复杂的模式。针对这些情况,就需要你自己来设计具体的代码啦。不过还好<a href="https://github.com/magicalpanda/MagicalRecord" title="MagicalRecord" target="_blank" rel="noopener">MagicalRecord</a>的实现大部分采用category的形式,所以扩展起来也很便利。</p><h4 id="category"><a href="#category" class="headerlink" title="category"></a>category</h4><p>实际项目中,我尝试在三个地方做了点简单的扩展。</p><ol><li><p>数据import</p><p> 客户端有个managed object(简称mo)会在接口m和接口n(均返回json数据)中共用,mo的某个property a相应的有两个mapkey a1和a2(map时的优先级a1>a2)。正常逻辑中,a1用于m,a2用于n。但是实际情况是,由于后端接口要保证数据兼容性,接口n中同时包含了a1和a2都可以map到值的两个字段,更恶心的是只有a2对应的字段是有正确的值的,a1对应的字段值是null,因此基于map的优先级,使用import的方法总是取a1对应的null(import时会认为<code>[NSNull null]</code>是有效的值,并最终将value赋值成nil)。</p><p> 虽然调整两者的优先级可以解决这个问题,但谁又能保证接口m不会存在同样的问题呢,所以我就参考<a href="https://github.com/magicalpanda/MagicalRecord" title="MagicalRecord" target="_blank" rel="noopener">MagicalRecord</a>的方式,给NSManagedObject创建了一个category,增加了数据的预处理函数,保证能先把没用的<code>[NSNull null]</code>统一处理成nil值,那么在后续的import过程中也就避免了上述情况。</p></li><li><p>分页操作</p><p> 形式上<a href="https://github.com/magicalpanda/MagicalRecord" title="MagicalRecord" target="_blank" rel="noopener">MagicalRecord</a>对数据的操作行为很像数据库,但它竟然没提供分页相关的操作。基于某些需求,我也就顺便参考了它的代码风格,补充了一些分页操作函数。</p></li><li><p>core data stack</p><p> 实际开发过程中,可以发现有些对象数据不需要存入数据库,那么<a href="https://github.com/magicalpanda/MagicalRecord" title="MagicalRecord" target="_blank" rel="noopener">MagicalRecord</a>的默认的<a href="https://developer.apple.com/library/ios/documentation/DataManagement/Devpedia-CoreData/coreDataStack.html" title="coreDataStack" target="_blank" rel="noopener">core data stack</a>模型也就不足以满足需求。不过还好一个PersistentStoreCoordinator同时支持多个PersistentStore,也就是说可以同时有<code>NSSQLiteStoreType</code>和<code>NSInMemoryStoreType</code>。了解了这一点后,剩下的也就是需要稍微利用一下<a href="https://github.com/magicalpanda/MagicalRecord" title="MagicalRecord" target="_blank" rel="noopener">MagicalRecord</a>的一些方法自己实现整个stack的setup,实现起来并不复杂~</p></li></ol><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p><a href="https://github.com/magicalpanda/MagicalRecord" title="MagicalRecord" target="_blank" rel="noopener">MagicalRecord</a>确实是一个值得使用的开源库。但是在便利地使用它的同时,一定也要相应的了解一下其中的设计,做到扬长避短,物尽其用:)</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p>1)<a href="https://github.com/magicalpanda/MagicalRecord" title="MagicalRecord" target="_blank" rel="noopener">https://github.com/magicalpanda/MagicalRecord</a></p><p>2)<a href="http://yannickloriot.com/2012/03/magicalrecord-how-to-make-programming-with-core-data-pleasant/#sthash.n5uCiXVB.dpbs" title="Magical Record: how to make programming with Core Data pleasant" target="_blank" rel="noopener">“Magical Record: how to make programming with Core Data pleasant”</a></p><p>3)<a href="http://www.raywenderlich.com/56879/magicalrecord-tutorial-ios" title="magicalrecord-tutorial-ios" target="_blank" rel="noopener">magicalrecord-tutorial-ios</a></p><p>4)<a href="https://developer.apple.com/library/ios/documentation/DataManagement/Devpedia-CoreData/coreDataStack.html" title="coreDataStack" target="_blank" rel="noopener">coreDataStack</a></p><p>5)<a href="http://www.cimgf.com/2012/05/29/importing-data-made-easy/" title="IMPORTING DATA MADE EASY" target="_blank" rel="noopener">IMPORTING DATA MADE EASY</a></p><p>6)<a href="http://www.cocoanetics.com/2012/07/multi-context-coredata/" title="Multi-Context CoreData" target="_blank" rel="noopener">Multi-Context CoreData</a></p><p>7)<a href="http://www.cocoanetics.com/2013/02/zarra-on-locking/" title="Zarra on Locking" target="_blank" rel="noopener">Zarra on Locking</a></p><p>8)<a href="http://floriankugler.com/blog/2013/4/29/concurrent-core-data-stack-performance-shootout" title="Concurrent Core Data Stacks – Performance Shootout" target="_blank" rel="noopener">Concurrent Core Data Stacks – Performance Shootout</a></p><p>9)<a href="http://floriankugler.com/blog/2013/5/11/backstage-with-nested-managed-object-contexts" title="Backstage with Nested Managed Object Contexts" target="_blank" rel="noopener">Backstage with Nested Managed Object Contexts</a></p>]]></content>
<summary type="html">
浅析MagicalRecord
</summary>
<category term="iOS-dev" scheme="https://ddrccw.github.io/categories/iOS-dev/"/>
<category term="MagicalRecord" scheme="https://ddrccw.github.io/tags/MagicalRecord/"/>
<category term="core data" scheme="https://ddrccw.github.io/tags/core-data/"/>
</entry>
<entry>
<title>在越狱设备上如何方便地主动更新测试版本app</title>
<link href="https://ddrccw.github.io/2014/04/13/2014-04-13-distribute-a-beta-app-to-jailbreak-device/"/>
<id>https://ddrccw.github.io/2014/04/13/2014-04-13-distribute-a-beta-app-to-jailbreak-device/</id>
<published>2014-04-13T06:02:53.000Z</published>
<updated>2018-08-11T09:43:45.742Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>之前有段时间申请到qq测试版本,在使用过程中,发现qq测试版本的升级过程有点小意思。看过之前写的《<a href="!--1--">再谈使用shell脚本build并创建ipa文件</a>》或者网上其他类似文章关于over-the-air分发app的应该都知道,常见的app分发需要用户跳到专门的下载页面去获取新版的app,但是qq测试版本的升级体验则不同,同样是主动提示更新,但它只要在alert上点击确定就可以马上下载安装最新的测试版本,省略了跳转的步骤。个人感觉这样在体验上蛮好,所以就尝试研究了一下。</p><h2 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h2><h3 id="为什么"><a href="#为什么" class="headerlink" title="为什么"></a>为什么</h3><p>看下文之前,估计还是有人会问为什么会有这样奇怪的需求。这里我觉得还是从用户体验的角度上理解比较简单,个人觉得它可以用在如下两个地方:</p><ol><li>公测。类似qq这种用户量大的app,每个新功能的推出都可能造成比较大的影响。为了提早获得市场反馈,针对性的邀请一些使用越狱设备的用户提前使用新功能,进而获取相关的使用数据来改进最终发布的版本上的功能是有必要的。</li><li>内测。相比以前需要相关的app使用者定期的去固定的页面获取最新的测试版本,主动提示更新并下载自然方便了许多,进而也就提高大家的工作效率。</li></ol><p>上述两个需求通过app store的发布方式显然是无法满足的,一方面,app store 不会让带测试字样的app审核通过,另一方面,app store发布的速度实在不可控。。。另外,据说TestFlight支持邀请用户的功能,我没试过也就没有发言权啦:p</p><h3 id="怎么做"><a href="#怎么做" class="headerlink" title="怎么做"></a>怎么做</h3><p>其实最基本的原理还是over-the-air的那套机制,只不过稍微深入地挖掘一下其中的一些可控之处,权当抛砖引玉~</p><h4 id="越狱检测"><a href="#越狱检测" class="headerlink" title="越狱检测"></a>越狱检测</h4><p>关于这点,网上应该有不少文章介绍了。</p><ol><li><p>某女程序媛的《<a href="http://danqingdani.blog.163.com/blog/static/1860941952012102122847478/" title="danqingdani.blog.163.com" target="_blank" rel="noopener">越狱检测/越狱检测绕过——xCon</a>》这篇文章写得蛮好的。另外,她blog里面提到的一本书《O’Reilly.Hacking.and.Securing.iOS.Applications》如果有时间的话,应该读一读学习下。</p></li><li><p>吴发伟翻译的《<a href="http://wufawei.com/2013/11/ios-application-security-24/" title="wufawei.com" target="_blank" rel="noopener">iOS应用程序安全</a>》系列亦可一读。</p></li><li><p>《<a href="http://theiphonewiki.com/wiki/Bypassing_Jailbreak_Detection" title="Bypassing_Jailbreak_Detection" target="_blank" rel="noopener">Bypassing Jailbreak Detection</a>》则写得比较概括点。</p></li></ol><p>另外,点击<a href="https://gist.github.com/ddrccw/8412847" title="Jailbreak_Detection" target="_blank" rel="noopener">此处</a>,则是我参考几篇文章实现简单检测的一些code。</p><p>注:在6.1.x的越狱设备上,我用尝试在private目录中写入文件的方式检测似乎不行。</p><h4 id="后台服务"><a href="#后台服务" class="headerlink" title="后台服务"></a>后台服务</h4><p>对移动端而言,只要明白如何发一个正确的请求即可。ipa的请求校验和传输更多的需要后台服务的支持。</p><p>其实,要实现qq那种效果的下载,最最基本的是要模拟常规安装页面那里的点击下载请求。在app中添加代码如下:</p><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">NSString</span> *serviceUrl = <span class="string">@"itms-services://?action=download-manifest&url=<plist-encoded-url>"</span>;</span><br><span class="line">[[<span class="built_in">UIApplication</span> sharedApplication] openURL:[<span class="built_in">NSURL</span> URLWithString:serviceUrl]];</span><br></pre></td></tr></table></figure><p>其他只需要沿用《<a href="!--1--">再谈使用shell脚本build并创建ipa文件</a>》中产出的相关文件及部署。那么这样看是不是很简单,如果只是内测用的话,到这里其实也就差不多了。但是从公测的角度上看,对安全比较敏感的童鞋应该不会止步于此。</p><p><strong>可控之处</strong></p><p>前面提到的可控之处,其实也就在于那个url的使用。用Charles捕捉一下请求,可以发现上述两行代码交给iOS处理后的结果,就是利用里面的url参数发了个GET请求,最终在response的body里面返回指定的plist文件。</p><p>同理,你可以发现plist里面的ipa以及icon的url也是可以操作的,其中ipa的url会发一个HEAD和一个GET请求(需要在GET请求中返回ipa包数据),icon的url则是会发一个GET请求。</p><p>既然url可以定制,相信后台童鞋也可以利用起来做检验或其他操作。</p><p>这里直接附上我写的一个简单<a href="https://github.com/ddrccw/DistributionDemo" title="DistributionDemo" target="_blank" rel="noopener">demo</a> :)</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>总之,原理是十分简单的,但是如何换种思维来改进用户体验确实是一个值得思考的问题~</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p>1)<a href="http://danqingdani.blog.163.com/blog/static/1860941952012102122847478/" title="danqingdani.blog.163.com" target="_blank" rel="noopener">http://danqingdani.blog.163.com/blog/static/1860941952012102122847478/</a></p><p>2)<a href="http://wufawei.com/2013/11/ios-application-security-24/" title="wufawei.com" target="_blank" rel="noopener">http://wufawei.com/2013/11/ios-application-security-24/</a></p><p>3)<a href="http://theiphonewiki.com/wiki/Bypassing_Jailbreak_Detection" title="Bypassing_Jailbreak_Detection" target="_blank" rel="noopener">http://theiphonewiki.com/wiki/Bypassing_Jailbreak_Detection “Bypassing_Jailbreak_Detection</a></p>]]></content>
<summary type="html">
在越狱设备上如何方便地主动更新测试版本app
</summary>
<category term="iOS-dev" scheme="https://ddrccw.github.io/categories/iOS-dev/"/>
<category term="iOS" scheme="https://ddrccw.github.io/tags/iOS/"/>
<category term="jailbreak" scheme="https://ddrccw.github.io/tags/jailbreak/"/>
<category term="beta-test" scheme="https://ddrccw.github.io/tags/beta-test/"/>
</entry>
<entry>
<title>Macports安装fontcustom制作中文图标字体</title>
<link href="https://ddrccw.github.io/2014/01/05/2014-01-05-how-to-make-icon-fonts-from-fontcustom-installed-by-macports/"/>
<id>https://ddrccw.github.io/2014/01/05/2014-01-05-how-to-make-icon-fonts-from-fontcustom-installed-by-macports/</id>
<published>2014-01-05T12:47:06.000Z</published>
<updated>2018-08-11T09:43:45.742Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最近在公司前端群里看到了关于图标字体的文章分享,不禁想到了之前star的<a href="https://github.com/lexrus/fontdiao" title="fontdiao" target="_blank" rel="noopener">fontdiao</a>项目中的关于中文图标的使用。那个项目中介绍的工具通过Homebrew安装较为容易,但是MacPorts安装起来的则有些问题。因为我一直用的MacPorts,而且当时也没在网上搜到相关的解决办法,遂搁置了下来。最近刚好有空闲时间,就趁机研究了一下。</p><h2 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h2><h3 id="安装fontcustom"><a href="#安装fontcustom" class="headerlink" title="安装fontcustom"></a>安装fontcustom</h3><p>首先追本溯源,制作图标字体的关键是<a href="https://github.com/alexdrone/ios-fontawesome" title="ios-fontawesome" target="_blank" rel="noopener">fontcustom</a>。从它的安装说明中可以看到它的显式依赖东东有fontforge,ttfautohint。</p><h4 id="fontforge"><a href="#fontforge" class="headerlink" title="fontforge"></a>fontforge</h4><p>安装这个库是最折腾的。虽然port上可以安装上,但是实际配合fontcustom使用时会报错</p><figure class="highlight ruby"><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">debug Copyright (c) <span class="number">2000</span>-<span class="number">2012</span> by George Williams.</span><br><span class="line"> Executable based on sources from <span class="number">14</span><span class="symbol">:</span><span class="number">57</span> GMT <span class="number">31</span>-Jul-<span class="number">2012</span>-ML-NoPython.</span><br><span class="line"> Library based on sources from <span class="number">14</span><span class="symbol">:</span><span class="number">57</span> GMT <span class="number">31</span>-Jul-<span class="number">2012</span>.</span><br><span class="line"> /Users/ddrccw/.rvm/gems/ruby-<span class="number">2.0</span>.<span class="number">0</span>-p247/gems/fontcustom-<span class="number">1.3</span>.<span class="number">1</span>/lib/fontcustom/scripts/generate.py 行: <span class="number">2</span> Undefined <span class="symbol">variable:</span> import</span><br><span class="line">error <span class="string">`fontforge`</span> compilation failed.</span><br></pre></td></tr></table></figure><p>看样子似乎是port安装的fontforge不支持Python导致的原因。无奈之下,只有选择从源码安装。</p><p><strong> 第一关 </strong></p><p>从<a href="http://sourceforge.net/projects/fontforge/files/fontforge-source/" title="fontforge-source" target="_blank" rel="noopener">这里</a>下fontforge源码和<a href="http://sourceforge.net/projects/freetype/files/freetype2/" title="freetype2" target="_blank" rel="noopener">这里</a>下freetype2的源码(因为fontforge的编译也需要freetype2的源码)。虽然实际上fontforge的<a href="http://fontforge.org/source-build.html#Dependencies" title="Dependencies" target="_blank" rel="noopener">依赖</a>有很多,但还好前面port的时候也下好了相关依赖的库文件,唯一还需要安装的依赖库是pango(同样是源码编译需要),这个也能从port上安装。另外,如果你需要fontforge运行有UI界面,还需要X11,这个可从<a href="http://xquartz.macosforge.org/trac/wiki/Releases" title="xquartz" target="_blank" rel="noopener">这里</a>下载。</p><p>这里小结一下源码安装fontforge需要准备的步骤:</p><ul><li>下载fontforge源码和freetype2的源码</li><li>port install fontforge pango,然后port uninstall fontforge(因为会和源码安装的冲突)</li><li>下载XQuartz并安装(可选)</li></ul><p>经过上面步骤,第一关也就是fontforge的源码编译前<code>./configure</code>的过程可以保证通过。</p><p><strong> 第二关 </strong></p><p>接下来就是编译。编译过程会出现两个问题。注:如果你使用的是lion下的xcode 4.x可能不会有下述问题。</p><p>1)第一个问题,有3个c文件(fontforge/macbinary.c fontforge/startui.c gutils/giomime.c)会编译失败,参考如下:</p><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"></span><br><span class="line">giomime.c:68:10: fatal error: '/Applications/Xcode.app/Contents/Developer/Headers/FlatCarbon/Files.h' file not found</span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"></Applications/Xcode.app/Contents/Developer/Headers/FlatCarbon/Files.h></span></span></span><br><span class="line"> ^</span><br><span class="line"><span class="number">1</span> error generated.</span><br></pre></td></tr></table></figure><p>显然这是头文件的问题。因为原先代码引用的头文件从MacOSX10.8.sdk开始位置发生了变化。解决方法,要么参考<a href="https://github.com/FontCustom/fontcustom/" title="fontcustom" target="_blank" rel="noopener">fontforge.patch</a>改相关源文件,要么直接用MacOSX10.7.sdk来编译。建议前者的说。</p><p>如果你选择后者,请走下列步骤:</p><ul><li><p>获得一份MacOSX10.7.sdk</p><p> 因为最新的xcode 5不包含MacOSX10.7.sdk,所以你需要从xcode 4.6.x的<code>Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs</code>目录中找到MacOSX10.7.sdk,并拷贝一份出来。说句题外话,为了减少sdk文件对xcode的依赖,我单独把它放到一个文件夹下,然后在xcode 5的和前面提到的同样路径的目录中做了一个软连接,让xcode 5能找到它。</p></li><li><p>保证编译时能找到MacOSX10.7.sdk</p><p> 将MacOSX10.7.sdk/Developer做个软连接(注意使用绝对路径,不然有坑。。)到根目录的/Developer即可。</p></li></ul><p>2)第二个问题,其编译报错如下:</p><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">/System/Library/Frameworks/ApplicationServices.framework/Frameworks/ATS.framework/Headers/SFNTLayoutTypes.h:1791:41: error: typedef redefinition with different types ('struct AnchorPoint' vs 'struct anchorpoint')</span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">AnchorPoint</span> <span class="title">AnchorPoint</span>;</span></span><br><span class="line"> ^</span><br><span class="line">./splinefont.h:<span class="number">512</span>:<span class="number">3</span>: note: previous definition is here</span><br><span class="line">} AnchorPoint;</span><br><span class="line"> ^</span><br></pre></td></tr></table></figure><p>同样,原因是显而易见的,即存在结构体重复定义的问题。要么参考这位老兄的<a href="https://github.com/Homebrew/homebrew/issues/18046#issuecomment-14355327" target="_blank" rel="noopener">做法</a>,改动一下fontforge/startui.c这个文件即可。要么用MacOSX10.7.sdk来编译。</p><p>最后说到这里,你可能会疑问Homebrew是怎么处理的。参考Homebrew的<a href="https://github.com/Homebrew/homebrew/blob/d73a1502848808c764023ef1f8d7af514cfef5b9/Library/Formula/fontforge.rb" title="fontforge.rb" target="_blank" rel="noopener">fontforge.rb</a>,因为包管理工具不会去改源码(吐槽一下:fontforge确实一年多没release新版本了。。),所以它的选择是对<code>fontforge/macbinary.c fontforge/startui.c gutils/giomime.c</code>这三个文件单独用MacOSX10.7.sdk编译。另外可以在<a href="https://github.com/Homebrew/homebrew/issues/13635" target="_blank" rel="noopener">#issues-13635</a>看到相关的讨论。</p><h3 id="ttfautohint"><a href="#ttfautohint" class="headerlink" title="ttfautohint"></a>ttfautohint</h3><p>ttfautohint在MacPorts上是找不到的,所以请去<a href="http://sourceforge.net/projects/freetype/files/ttfautohint/" title="ttfautohint" target="_blank" rel="noopener">这里</a>下载编译好的二进制文件,放到系统找得到的bin目录下即可,比如<code>/opt/local/bin/</code>。</p><p>准备好了fontforge,ttfautohint,最后<code>gem install fontcustom</code>就ok了。</p><h3 id="制作图标字体"><a href="#制作图标字体" class="headerlink" title="制作图标字体"></a>制作图标字体</h3><p>其实经过前面的折腾,剩下的基本就so easy了。主要就是准备好svg,写好yml配置文件,最后用fontcustom compile一遍即可,具体请参考<a href="https://github.com/FontCustom/fontcustom" title="fontcustom" target="_blank" rel="noopener">fontcustom</a>。但是结合到实际移动端项目中,这也才是第一步。关键是如何利用fontcustom的模板功能生成移动端能用的代码。</p><p>就iOS开发而言,你可以通过继承的方式或Category的方式扩展对象。</p><ul><li><p><a href="https://github.com/alexdrone/ios-fontawesome" title="ios-fontawesome" target="_blank" rel="noopener">ios-fontawesome</a>项目就采用继承的方式实现对图标字体的利用,不过没提供模板,你只能用现成的图标字体。</p></li><li><p><a href="https://github.com/lexrus/fontdiao" title="fontdiao" target="_blank" rel="noopener">fontdiao</a>项目则是采用Category的方式,而且也提供了模板,不过它的配置不适用于最新版的fontcustom,这个需要替换配置,也不难。最后替换它的svg为自己项目的svg(这里我用<a href="http://icomoon.io/app/#/select" title="iconmoon" target="_blank" rel="noopener">iconmoon</a>的svg实验了一下,发现不一定要照它说明的使用300x300的svg文件),然后compile一下,也就达到自定义图标字体的目的了,而且也提供了配套的ios代码文件。</p></li></ul><p>当然你也可以参考这两个项目,实现自己的代码:)</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p>1)<a href="https://github.com/lexrus/fontdiao" title="fontdiao" target="_blank" rel="noopener">https://github.com/lexrus/fontdiao</a></p><p>2)<a href="https://github.com/alexdrone/ios-fontawesome" title="ios-fontawesome" target="_blank" rel="noopener">https://github.com/alexdrone/ios-fontawesome</a></p><p>3)<a href="https://github.com/FontCustom/fontcustom/" title="fontcustom" target="_blank" rel="noopener">https://github.com/FontCustom/fontcustom/</a></p><p>4)<a href="https://gist.github.com/ummels/3419350" title="fontforge.patch" target="_blank" rel="noopener">https://gist.github.com/ummels/3419350</a></p>]]></content>
<summary type="html">
Macports安装fontcustom制作中文图标字体
</summary>
<category term="iOS-dev" scheme="https://ddrccw.github.io/categories/iOS-dev/"/>
<category term="iOS" scheme="https://ddrccw.github.io/tags/iOS/"/>
<category term="macports" scheme="https://ddrccw.github.io/tags/macports/"/>
<category term="fontcustom" scheme="https://ddrccw.github.io/tags/fontcustom/"/>
<category term="icon-fonts" scheme="https://ddrccw.github.io/tags/icon-fonts/"/>
</entry>
<entry>
<title>再谈使用shell脚本build并创建ipa文件</title>
<link href="https://ddrccw.github.io/2013/09/16/2013-09-16-daily-build-and-create-ipd-using-shell-script-2/"/>
<id>https://ddrccw.github.io/2013/09/16/2013-09-16-daily-build-and-create-ipd-using-shell-script-2/</id>
<published>2013-09-15T16:05:41.000Z</published>
<updated>2018-08-11T15:06:28.004Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>关于这个话题,其实在《<a href="https://ddrccw.github.io/2013/09/16/2013-09-16-daily-build-and-create-ipd-using-shell-script-2/">使用shell脚本build并创建ipa文件</a>)》一文中已经记录过一次。之所以老调重弹,也是因为在新项目的使用过程中,发现那个脚本尚有改进的余地。</p><h2 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h2><p>首先,要感谢lexrus的<a href="https://github.com/lexrus/ios-makefile" title="ios-makefile" target="_blank" rel="noopener">ios-makefile</a>项目,里面确实有不少可以借鉴学习的地方。虽然那个项目提供的脚本包含了一些蛮有用的功能,但是结合到我现在做的项目,还是有些不能满足我的需求。本来想偷懒直接用来着,看来还是得改改orz。。</p><h3 id="需求"><a href="#需求" class="headerlink" title="需求"></a>需求</h3><p><strong>基本的需求</strong></p><p>就是<a href="https://ddrccw.github.io/2013/09/16/2013-09-16-daily-build-and-create-ipd-using-shell-script-2/">前文</a>所实现的内容:能够从代码产出一份ipa,并且能通过相应的web页面安装。</p><p><strong>额外的需求</strong></p><p>随着app的版本升级,用户群中是可能同时存在多个版本的。因为不像企业类型app,可以通过MDM强制客户端升级,而且强制升级的体验不是很好,所以用户客户端的版本升级是不可控的,开发以及测试人员能保障的就是让版本升级后app能够正常使用,因而,挨个的从历史版本升级到最新版本的测试是必要的。但是app一但提交到app store,历史版本就不见了,所以不能指望app store,需要在每次完成一次版本后,做一份ipa等文件的快照,方便后续的升级测试。</p><p>总之一句话概括需求,就是需要做简单的版本分发管理。</p><h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><p>针对第一个需求,网上找的大部分脚本基本够用。</p><p>针对第二个需求,其实思路也很简单,就是多维护一个页面入口,提供对历史版本的访问。</p><p>下面记录一下我在实际项目中采用的解决方案。</p><p>a) 为了方便管理,我直接通过我的开发机搭建web服务。感谢ML自带Apache HTTP Server,搭建web服务很简单。这里推荐一个小工具<a href="http://clickontyler.com/web-sharing/" title="web-sharing" target="_blank" rel="noopener">web sharing</a>,我就直接通过它来开关web服务:)</p><p>b) 将<a href="https://gist.github.com/ddrccw/6596464" title="new package shell" target="_blank" rel="noopener">这里</a>的3个文件,放到项目的目录下,配置好里面的相关参数。运行makefile里的命令,顺利的话,最后会在web服务的目录下看到如下结构:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><webRoot>/</span><br><span class="line"><project_name>/ #测试 最新版本</span><br><span class="line"><project_name-distribution>/ #线上</span><br><span class="line"><Vx.x.x>/ #历史版本</span><br><span class="line">latest/ #最新版本</span><br></pre></td></tr></table></figure><p>在项目中,我习惯分发两类的ipa。一类是单纯利用测试环境数据的版本,另一类就是接近或直接使用线上环境数据的版本。只有对后者我会每次在上线后用稳定代码编译一份留存。</p><p>c) 最后将其编译结果提示的url告诉测试的小伙伴们,步骤基本也就差不多啦。</p><p><strong>ps:关于那3个文件的介绍</strong></p><p>a. package.sh </p><pre><code>核心文件,包含编译,打包,生成html页面,以及将最终产出的文件部署到对应目录的功能</code></pre><p>b. Makefile</p><pre><code>基于package.sh的一组命令。</code></pre><p>c. package.plist</p><pre><code>因为分发的两类ipa可以是不同的sheme编译出来的,为了方便同步两个scheme的通用信息,比如version number和build number,所以就考虑使用这个公共的plist文件来共享。</code></pre><h2 id="引申"><a href="#引申" class="headerlink" title="引申"></a>引申</h2><ol><li><p>使用scheme,build setting编译不同版本</p><p> 虽然默认的项目模板是一个target对应两套configurations来管理,但是开发过程中总觉得快速切换debug和release还是不怎么方便,所以我采用的是至少通过两个scheme的方式,分别对应debug和release,前者可能会加入一些类似DCIntrospect这种用来专门调试用的第三方库,后者则是保证代码最精简地引入。 另外说是“至少”,是考虑到可能还有其他的需求需要单独使用scheme。不过这里还有个小坑,就是添加新代码文件的时候,可能忘了在相应的target中添加(使用release版本bug bash过程中出现过的一个crash就是因为这点)。所有建议使用默认的sheme做release用,后加的scheme做开发用,在某种程度上减少这种失误的出现概率。</p><p> 如果只是通过编译来划分版本,其实用不同的configuration settings file也是可以达到管理的目的的。这里也就不赘述。</p><p> 《<a href="http://www.youtube.com/watch?v=I5RqcYzrY4Y" title="How We Built Facebook for iOS" target="_blank" rel="noopener">How We Built Facebook for iOS</a>》(要翻墙)中分享的关于multiple builds只是简单的提到用不同的provision file,可能他们的版本控制更严格吧,具体俺们也不知道啦~~</p></li><li><p>图片处理</p><p> 让icon带上build number信息的convert命令,是来自安装的ImageMagick,使用请参考<a href="http://www.imagemagick.org/Usage/" title="imagemagick usage" target="_blank" rel="noopener">官网使用说明</a>。</p></li><li><p>version number & build number</p><p> 两者分别对应CFBundleShortVersionString、CFBundleVersion。</p><p> 前者对于用户而言是最直观的,在每次准备release到app store前,做一次变化,比较普遍的是设置成三位,格式如下</p><blockquote><p>{MajorVersion}.{MinorVersion}.{Revision}</p><ul><li>Major version - Major changes, redesigns, and functionality changes</li><li>Minor version - Minor improvements, additions to functionality</li><li>Revision - A patch number for bug-fixes</li></ul></blockquote><p> 后者更多的是对开发测试者而言更有意义,用以区分编译的版本。有的人喜欢做个shell在xcode build时自动递增。但我在项目中使用时,主要是在发布测试版时,才变化该值。因为开发者自己测试时,使用的肯定都是最新的代码,个人感觉build number对我没啥用。但是发布测试就不一样了,有时测试人员忘记更新最新版,会拿旧版跟你报bug,为了明确他们使用的编译版本,这时利用好build number可以免除一些不必要的沟通。以上只是我个人开发过程中使用的规则。当然,使用的原则还是怎么方便就怎么用:)。</p><p> 推荐看下《<a href="http://stackoverflow.com/questions/6851660/version-vs-build-in-xcode-4" target="_blank" rel="noopener">Version vs build in XCode 4</a>》中的讨论,应该会有所收获。</p><p> 另外,如果CFBundleVersion是自增的数字,发布时新版的CFBundleVersion不比旧版的大的话,会提交失败。这里也顺便记录一下。</p></li></ol><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p>1)<a href="https://gist.github.com/ddrccw/6596464" title="new package shell" target="_blank" rel="noopener">https://gist.github.com/ddrccw/6596464</a></p><p>2)<a href="https://github.com/lexrus/ios-makefile" title="ios-makefile" target="_blank" rel="noopener">https://github.com/lexrus/ios-makefile</a></p><p>3)<a href="http://clickontyler.com/web-sharing/" title="web-sharing" target="_blank" rel="noopener">http://clickontyler.com/web-sharing</a></p><p>4)<a href="http://www.imagemagick.org/Usage/" title="imagemagick usage" target="_blank" rel="noopener">http://www.imagemagick.org/Usage</a></p><p>5)<a href="http://www.youtube.com/watch?v=I5RqcYzrY4Y" title="How We Built Facebook for iOS" target="_blank" rel="noopener">http://www.youtube.com/watch?v=I5RqcYzrY4Y</a></p><p>6)<a href="http://stackoverflow.com/questions/6851660/version-vs-build-in-xcode-4" target="_blank" rel="noopener">http://stackoverflow.com/questions/6851660/version-vs-build-in-xcode-4</a></p><p>7)<a href="http://stackoverflow.com/questions/7281085/whats-the-difference-between-version-number-in-itunes-connect-bundle-versio" target="_blank" rel="noopener">http://stackoverflow.com/questions/7281085/whats-the-difference-between-version-number-in-itunes-connect-bundle-versio</a></p><p>8)<a href="http://www.blog.montgomerie.net/easy-iphone-application-versioning-with-agvtool" target="_blank" rel="noopener">http://www.blog.montgomerie.net/easy-iphone-application-versioning-with-agvtool</a></p><p>9)<a href="http://chanson.livejournal.com/125568.html" target="_blank" rel="noopener">http://chanson.livejournal.com/125568.html</a></p>]]></content>
<summary type="html">
再谈使用shell脚本build并创建ipa文件
</summary>
<category term="iOS-dev" scheme="https://ddrccw.github.io/categories/iOS-dev/"/>
<category term="iOS" scheme="https://ddrccw.github.io/tags/iOS/"/>
<category term="shell" scheme="https://ddrccw.github.io/tags/shell/"/>
<category term="ipa" scheme="https://ddrccw.github.io/tags/ipa/"/>
<category term="daily build" scheme="https://ddrccw.github.io/tags/daily-build/"/>
<category term="build setting" scheme="https://ddrccw.github.io/tags/build-setting/"/>
</entry>
<entry>
<title>记分享模块开发遇到的几个问题</title>
<link href="https://ddrccw.github.io/2013/06/29/2013-07-01-include-url-in-link/"/>
<id>https://ddrccw.github.io/2013/06/29/2013-07-01-include-url-in-link/</id>
<published>2013-06-29T02:54:10.000Z</published>
<updated>2018-08-11T09:43:45.741Z</updated>
<content type="html"><![CDATA[<h2 id="正题"><a href="#正题" class="headerlink" title="正题"></a>正题</h2><h3 id="Url-Encode"><a href="#Url-Encode" class="headerlink" title="Url Encode"></a>Url Encode</h3><h4 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h4><p>给第三方分享链接的url传递的参数中包含自定义的url,其中自定义的url中包含<code>&</code>符号,导致最终在第三方web端分享界面显示时,缺少自定义url的<code>&</code>符号后面的内容。</p><h4 id="分析和处理"><a href="#分析和处理" class="headerlink" title="分析和处理"></a>分析和处理</h4><p>这个应该是一个典型的url encode的问题。</p><p>说到url encode。首先参考<a href="http://www.ietf.org/rfc/rfc2396.txt" title="rfc2396" target="_blank" rel="noopener">rfc2396</a>和<a href="http://www.ietf.org/rfc/rfc3986.txt" title="rfc3986" target="_blank" rel="noopener">rfc3986</a>。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">The generic URI syntax consists of a hierarchical sequence of</span><br><span class="line">components referred to as the scheme, authority, path, query, and</span><br><span class="line">fragment.</span><br><span class="line"></span><br><span class="line">URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]</span><br></pre></td></tr></table></figure><p>一般来说,一个url符合如上规范,是由多个component组成的。每个component中可能还包含子component,比如query中可能会有多个参数。因此,就规范定义了一些保留字符用来分割界定这些component,对应前面的例子就是参数之间的<code>&</code>符号。如果需要在传递的参数中包含保留字符,就需要url encode,把保留字符转义成<code>%HEXHEX</code>的格式。另外,还有非保留字符和包含有特定意义的标识数据(Identifying Data)。</p><p>非保留字符,相对于保留字符,在URI中没有需要保留的意义。<a href="http://www.ietf.org/rfc/rfc2396.txt" title="rfc2396" target="_blank" rel="noopener">rfc2396</a>和<a href="http://www.ietf.org/rfc/rfc3986.txt" title="rfc3986" target="_blank" rel="noopener">rfc3986</a>中都建议不要encode。</p><p>特定意义的标识数据(Identifying Data)。大部分情况下,URI中的非保留字符是基于US-ASCII编码解释的。但是实际系统中会有超出US-ASCII字符集以外编码的字,比如UTF8,GBK等。为了保证这些字在特定编码下的含义,就需要encode。</p><p>以下附上<a href="http://www.ietf.org/rfc/rfc2396.txt" title="rfc2396" target="_blank" rel="noopener">rfc2396</a>和<a href="http://www.ietf.org/rfc/rfc3986.txt" title="rfc3986" target="_blank" rel="noopener">rfc3986</a>的保留字符和非保留字符</p><p><a href="http://www.ietf.org/rfc/rfc2396.txt" title="rfc2396" target="_blank" rel="noopener">rfc2396</a> 中,</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" |</span><br><span class="line"> "$" | ","</span><br><span class="line">unreserved = alphanum | mark</span><br><span class="line">mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"</span><br></pre></td></tr></table></figure><p><a href="http://www.ietf.org/rfc/rfc3986.txt" title="rfc3986" target="_blank" rel="noopener">rfc3986</a> 中,</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">reserved = gen-delims / sub-delims</span><br><span class="line">gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"</span><br><span class="line">sub-delims = "!" / "$" / "&" / "'" / "(" / ")"</span><br><span class="line"> / "*" / "+" / "," / ";" / "="</span><br><span class="line">unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"</span><br></pre></td></tr></table></figure><p>回到我遇到的问题上来,<code>&</code>字符是保留字符,如果要作为参数的一部分,就需要在encode函数中的转义字符集中加上<code>&</code>字符。</p><p>有关objctive-c中如何使用转义函数,可以参考《<a href="http://www.winddisk.com/2012/05/26/objective_c_url_encode/" title="objective_c_url_encode" target="_blank" rel="noopener">Objective C URL编码转换</a>》。写的有理有据,令人信服~</p><p><strong>最后,再补上一些使用url encode的注意点</strong></p><ol><li>如果url中要传递的参数比较多,并且有嵌套引用的情况,一定要注意参数不要被二次编码。</li><li>不要被某些直接对整个url进行转义的例子迷惑(我被坑过T_T…),一定要理解哪些需要被转义哪些不需要被转义。</li><li>由于历史原因,application/x-www-form-urlencoded类型的数据中编码的规则可能会有不同,比如空格会被编码成<code>+</code>而不是<code>%20</code>。</li></ol><h3 id="Include-Url-In-Mail-Links"><a href="#Include-Url-In-Mail-Links" class="headerlink" title="Include Url In Mail Links"></a>Include Url In Mail Links</h3><h4 id="问题描述-1"><a href="#问题描述-1" class="headerlink" title="问题描述"></a>问题描述</h4><p>如何以mail links的形式,在邮件中添加超链接?</p><h4 id="分析和处理-1"><a href="#分析和处理-1" class="headerlink" title="分析和处理"></a>分析和处理</h4><p>mail links的格式,直接参考<a href="http://developer.apple.com/library/ios/#featuredarticles/iPhoneURLScheme_Reference/Articles/MailLinks.html" title="MailLinks" target="_blank" rel="noopener">官方文档</a>。</p><p>虽然说往常要在邮件中添加富文本是通过MFMailComposeViewController的<code>setMessageBody:isHTML:</code>方法,而且有能在邮件中添加附件的操作,也比较方便,但是这里主要是尝试在app crash的同时,能马上发邮件报告crash信息,并稍微丰富一下邮件内容。(当然,在app重新启动的同时,检测log后再发邮件也可以,不过这是另外的话题了。。。)</p><p>考虑到邮件的内容是富文本的,而且有<code>setMessageBody:isHTML:</code>这个方法,基本可以确定邮件的内容是html格式。不过,同样要注意的是前面提到的encode问题。</p><p>例子代码如下:</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="built_in">NSString</span> *body = <span class="string">@"Hey!I wanted to send you this link to check out: <a href='http://www.google.com'>google</a>"</span>;</span><br><span class="line"><span class="built_in">NSString</span> *utfEncoded = [body stringByAddingPercentEscapesUsingEncoding:<span class="built_in">NSUTF8StringEncoding</span>]; </span><br><span class="line"><span class="built_in">NSString</span> *urlString = [<span class="built_in">NSString</span> stringWithFormat:@\<span class="string">"mailto:?subject=test&body=%@\", utfEncoded];</span></span><br><span class="line"><span class="string">NSURL *url = [[NSURL alloc] initWithString:urlString];</span></span><br><span class="line"><span class="string">[[UIApplication sharedApplication] openURL:url];</span></span><br></pre></td></tr></table></figure><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><p>1)<a href="http://www.ietf.org/rfc/rfc2396.txt" title="rfc2396" target="_blank" rel="noopener">rfc2396</a></p><p>2)<a href="http://www.ietf.org/rfc/rfc3986.txt" title="rfc3986" target="_blank" rel="noopener">rfc3986</a></p><p>3)<a href="http://www.winddisk.com/2012/05/26/objective_c_url_encode/" title="objective_c_url_encode" target="_blank" rel="noopener">Objective C URL编码转换</a></p><p>4)<a href="http://www.cocoanetics.com/2009/08/url-encoding/" title="url-encoding" target="_blank" rel="noopener">URL Encoding</a></p><p>5)<a href="http://blog.ericsk.org/archives/1423" title="混亂的 URLEncode" target="_blank" rel="noopener">混亂的 URLEncode</a></p><p>6)<a href="http://en.wikipedia.org/wiki/Percent-encoding" title="Percent-encoding" target="_blank" rel="noopener">Percent-encoding</a></p><p>7)<a href="http://developer.apple.com/library/ios/#featuredarticles/iPhoneURLScheme_Reference/Articles/MailLinks.html" title="MailLinks" target="_blank" rel="noopener">MailLinks</a></p><p>8)<a href="http://iphonedevsdk.com/forum/iphone-sdk-development/9527-include-url-in-email.html" title="include-url-in-email" target="_blank" rel="noopener">include-url-in-email</a></p>]]></content>
<summary type="html">
记分享模块开发遇到的几个问题
</summary>
<category term="iOS-dev" scheme="https://ddrccw.github.io/categories/iOS-dev/"/>
<category term="iOS" scheme="https://ddrccw.github.io/tags/iOS/"/>
<category term="url-encode" scheme="https://ddrccw.github.io/tags/url-encode/"/>
</entry>
<entry>
<title>unit testing in xcode</title>
<link href="https://ddrccw.github.io/2013/04/07/2013-04-07-unit-testing-in-xcode/"/>
<id>https://ddrccw.github.io/2013/04/07/2013-04-07-unit-testing-in-xcode/</id>
<published>2013-04-07T01:38:52.000Z</published>
<updated>2018-08-11T14:02:42.654Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>每次在xcode上建立一个new project,都会看到一个选项提示是否”Include Unit tests”。基于一些考虑,我也确实未在项目中使用过。如今,随着我开发学习的深入,一方面了解了一些TDD的概念,另一方面也确实在一些开源项目(如asi-http-request)中看到Unit test的身影。看来似乎有必要学习一下iOS开发的一些unit test知识,故也就有了此文。</p><h2 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h2><p>参考iOS开发单元测试的入门文章《<a href="http://www.raywenderlich.com/3716/unit-testing-in-xcode-4-quick-start-guide" title="unit-testing-in-xcode-4-quick-start-guide" target="_blank" rel="noopener">unit-testing-in-xcode-4-quick-start-guide</a>》,主要有两个比较常见的框架:OCUnit和GHUnit。另外还有一个单元测试中专门用于Mock对象的OCMock。下面容我一一道来。</p><h3 id="OCUnit"><a href="#OCUnit" class="headerlink" title="OCUnit"></a>OCUnit</h3><p>OCUnit是apple官方的单元测试框架,它的优点就是和xcode集成较好;缺点嘛,就是每次cmd+U执行test,所有的用例都会跑一遍,并且缺少相应的界面进行管理(只是在终端中输出)。试想一下,如果用例很多,开发过程中有时候只是反复跑某个case,而你需要在终端的所有输出中找那个case的结果,肯定会比较不方便。</p><h4 id="关于使用"><a href="#关于使用" class="headerlink" title="关于使用"></a>关于使用</h4><p>可以参看《<a href="http://developer.apple.com/library/mac/#documentation/developertools/Conceptual/UnitTesting/00-About_Unit_Testing/about.html#//apple_ref/doc/uid/TP40002143-CH1-SW1" title="Xcode Unit Testing Guide" target="_blank" rel="noopener">Xcode Unit Testing Guide</a>》,OCUnit提供两种单元测试</p><ol><li><p>Logic tests,顾名思义,只是测试代码逻辑,无关界面元素</p><blockquote><p>These tests check the correct functionality of a unit of code by itself (not in an app). With logic tests you can put together specific test cases to exercise a unit of code. You can also use logic tests to perform stress-testing of your code to ensure that it behaves correctly in extreme situations that are unlikely in a running app. These tests help you produce robust code that works correctly when used in ways that you did not anticipate.</p></blockquote></li><li><p>Application tests,appdelegate是有值的, app是运行状态的,所以可以测试ui的变化等</p><blockquote><p>These tests check units of code in the context of your app. You can use application tests to ensure that the connections of your user-interface controls (outlets and actions) remain in place, and that your controls and controller objects work correctly with your object model as you work on your app. You can also use these tests to perform hardware testing, such as getting the location of the device on which your app is running.</p></blockquote></li></ol><p><strong>注意点</strong></p><ol><li><p>Logic tests和Application tests针对模拟器都是可以进行的,但Logic tests在真机设备上是无法进行的。</p></li><li><p>在给项目添加单元测试时,尽管可以单独为单元测试建立一个scheme,测试人员可以只关注针对该scheme编写测试用例。但开发人员使用单元测试时,更倾向于在项目自身的scheme中包含单元测试模块,如图</p><p> <img src="/images/unit-testing-in-xcode/scheme.png" alt="alt scheme" title="scheme"></p><p> 在项目的scheme中添加单元测试的target依赖关系,这样就省的要在单元测试和项目的scheme之间进行频繁切换。</p></li><li><p>添加application tests的target后,一定要记得设置该target的building setting</p><ul><li><p>Bundle Loader的值</p><ul><li><p>iOS: $(BUILT_PRODUCTS_DIR)/\<app_name>.app/\<app_name></p></li><li><p>Mac: $(BUILT_PRODUCTS_DIR)/\<app_name>.app/Contents/MacOS/<\app_name></p></li></ul></li><li><p>Test Host的值</p><p> $(BUNDLE_LOADER)</p><p>设置好后,编译测试用例时就不用在build phases->compile sources里添加所有与用例有关的code文件,只需添加有直接关系即用例中用到的对象结构代码。</p></li></ul></li></ol><h3 id="GHUnit"><a href="#GHUnit" class="headerlink" title="GHUnit"></a>GHUnit</h3><p><a href="https://github.com/gabriel/gh-unit" title="GHUnit" target="_blank" rel="noopener">GHUnit</a>是第三方的开源框架。一方面它弥补了之前提到的OCUnit的不足之处,单元测试时它提供了一个专门的界面来管理测试用例。可以一次只运行某个case。另一方面,它还提供其他的一些强大功能,比如可直接在终端命令行进行单元测试。</p><p>引入GHUnit可以参考<a href="http://gabriel.github.io/gh-unit/docs/" title="GHUnit docs" target="_blank" rel="noopener">文档</a>, 下述两点千万不能遗漏:</p><ul><li><p>因为GHUnit自带了一个界面来显示Unit Test,需要删除xxxxTests目录下的AppDelegate.h/.m,另外还需要修改其中的main.m </p><ul><li><p>删除 #import “AppDelegate.h”</p></li><li><p>修改</p><p> <code>return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));</code></p></li></ul></li></ul><pre><code>为`return UIApplicationMain(argc, argv, nil, @“GHUnitIOSAppDelegate”);`</code></pre><ul><li>在xxxxTest Target的Building Setting中的Other Linker Flags中添加-ObjC -all_load</li></ul><p>还有一点是文档中未写明的,但实际引入过程中会报的问题:</p><figure class="highlight bash"><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">Undefined symbols <span class="keyword">for</span> architecture i386:</span><br><span class="line"> <span class="string">"_CACurrentMediaTime"</span>, referenced from:</span><br><span class="line"> _GHRunForInterval <span class="keyword">in</span> GHUnitIOS(GHTestUtils.o)</span><br><span class="line"> _GHRunUntilTimeoutWhileBlock <span class="keyword">in</span> GHUnitIOS(GHTestUtils.o)</span><br><span class="line">ld: symbol(s) not found <span class="keyword">for</span> architecture i386</span><br><span class="line">clang: error: linker <span class="built_in">command</span> failed with <span class="built_in">exit</span> code 1 (use -v to see invocation)</span><br></pre></td></tr></table></figure><p>只要添加QuartzCore.framework即可。</p><p>ps:直接利用源码build framework</p><ul><li><code>git clone https://github.com/gabriel/gh-unit.git</code></li><li>在<code>gh-unit/Project-iOS/</code>下make,build的framework位于<code>gh-unit/Project-iOS/build/Framework/GHUnitIOS.framework</code></li></ul><h4 id="关于使用-1"><a href="#关于使用-1" class="headerlink" title="关于使用"></a>关于使用</h4><ul><li><p>测试用例的写法可以参考<code>gh-unit/Tests</code>中的写法,还有《<a href="http://gabriel.github.com/gh-unit/docs/appledoc_include/guide_testing.html" title="guide testing" target="_blank" rel="noopener">guide_testing Document</a>》,</p></li><li><p>利用终端make test时,可能遇到的问题和处理,可参考<a href="https://github.com/gabriel/gh-unit/issues/96" title="issues" target="_blank" rel="noopener">issue 96</a></p></li><li><p>有得必有失,GHUnit无法和OCUnit一样利用bundle loader,需要在其Target->Build Phases->Compile Sources中引入所有与测试用例有关的项目代码。</p></li></ul><h3 id="OCMock"><a href="#OCMock" class="headerlink" title="OCMock"></a>OCMock</h3><p><a href="http://en.wikipedia.org/wiki/Mock_object" title="Mock Object" target="_blank" rel="noopener">Mock Object</a>技术是单元测试里的一种常见技术。简单的理解就是,在test某块代码逻辑时,通过方便的Mock代码逻辑所依赖的外部对象(可以是未开发完的),进而减少依赖,让测试者能更专注于测试待测试的单元。OCMock则是objective-c开发中对<a href="http://en.wikipedia.org/wiki/Mock_object" title="Mock Object" target="_blank" rel="noopener">Mock Object</a>的一种实现。</p><p>引入OCMock,请移步参看<a href="http://ocmock.org/ios/" title="ocmock" target="_blank" rel="noopener">官网</a>。如果没耐心看的话,iOS中使用OCMock也可以参考下面的流程:</p><ol><li>可以<a href="http://ocmock.org/download/" target="_blank" rel="noopener">down</a>一份库文件或选择运行git目录Tools里的build.rb,在Products目录下可以找到build出的库文件。</li><li>在用于Test的Target中添加OCMock的头文件(Header Search Path)和OCMock的静态库。</li><li>同样需要在Other Linker Flags中添加-ObjC -all_load(其实官方文档推荐用-force_load, 能指定路径载入,详情请见《<a href="http://developer.apple.com/library/mac/#qa/qa1490/_index.html" title="Apple's Technical Q&A QA1490" target="_blank" rel="noopener">Apple’s Technical Q&A QA1490</a>》)</li></ol><h4 id="关于使用-2"><a href="#关于使用-2" class="headerlink" title="关于使用"></a>关于使用</h4><p>因为我的经验也不是很多,就先简单引用下<a href="http://erik.doernenburg.com/2008/07/testing-cocoa-controllers-with-ocmock/" title="Testing Cocoa Controllers with OCMock" target="_blank" rel="noopener">OCMock作者</a>总结的一种应用模式吧 :)</p><blockquote><p>In summary, testing controllers becomes relatively easy when we follow this pattern:</p><ol><li>Replace all UI elements with mocks; using key-value coding to access the outlets.</li><li>Set up stubs with return values for UI elements that the controller will query.</li><li>Set up expectations for UI elements that the controller should manipulate.</li><li>Invoke the method in the controller.</li><li>Verify the expectations.</li></ol></blockquote><h2 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h2><p>1)<a href="http://www.raywenderlich.com/3716/unit-testing-in-xcode-4-quick-start-guide" title="unit-testing-in-xcode-4-quick-start-guide" target="_blank" rel="noopener">http://www.raywenderlich.com/3716/unit-testing-in-xcode-4-quick-start-guide</a></p><p>2)<a href="http://developer.apple.com/library/mac/#documentation/developertools/Conceptual/UnitTesting/00-About_Unit_Testing/about.html#//apple_ref/doc/uid/TP40002143-CH1-SW1" title="Xcode Unit Testing Guide" target="_blank" rel="noopener">Xcode Unit Testing Guide</a></p><p>3)<a href="http://gabriel.github.io/gh-unit/docs/" title="GHUnit docs" target="_blank" rel="noopener">GHUnit docs</a></p><p>4)<a href="http://gabriel.github.com/gh-unit/docs/appledoc_include/guide_testing.html" title="guide testing" target="_blank" rel="noopener">GHUnit guide testing</a></p><p>5)<a href="https://github.com/gabriel/gh-unit/issues/96" title="issues" target="_blank" rel="noopener">Command Line Tests Fail – Xcode 4.5</a></p><p>6)<a href="http://developer.apple.com/library/mac/#qa/qa1490/_index.html" title="Building Objective-C static libraries with categories" target="_blank" rel="noopener">Building Objective-C static libraries with categories</a></p>]]></content>
<summary type="html">
unit testing in xcode
</summary>
<category term="iOS-dev" scheme="https://ddrccw.github.io/categories/iOS-dev/"/>
<category term="iOS" scheme="https://ddrccw.github.io/tags/iOS/"/>
<category term="Unit-testing" scheme="https://ddrccw.github.io/tags/Unit-testing/"/>
<category term="OCUnit" scheme="https://ddrccw.github.io/tags/OCUnit/"/>
<category term="GHUnit" scheme="https://ddrccw.github.io/tags/GHUnit/"/>
<category term="OCMock" scheme="https://ddrccw.github.io/tags/OCMock/"/>
</entry>
<entry>
<title>利用UIWebView实现富文本编辑器</title>
<link href="https://ddrccw.github.io/2013/03/08/2013-03-08-how-to-make-a-rich-text-editor-in-uiwebview/"/>
<id>https://ddrccw.github.io/2013/03/08/2013-03-08-how-to-make-a-rich-text-editor-in-uiwebview/</id>
<published>2013-03-08T08:51:28.000Z</published>
<updated>2018-08-11T14:08:14.459Z</updated>
<content type="html"><![CDATA[<h2 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h2><p>最近的一个ipad项目有需求,需要在iOS客户端上实现一个笔记功能。由于之前的一个版本对于笔记的功能要求不高,我也只是用UITextView实现了简单的录入。但是接下来的一个版本对于笔记的功能则是希望有web端富文本编辑器的所见即所得的效果。</p><p>刚开始,我还以为只能通过Core Text实现。如果要考虑基于HTML实现录入内容的跨平台展现,可能还需要做一番HTML到NSAttributeString的对应转换,而且很多东西如复制要自己实现,比较麻烦。</p><p>后来,上网查阅了一番,经tx的<a href="http://f2e.us/slides/iOS_Rich_Editor/iOS_Rich_Editor.html#slide1" target="_blank" rel="noopener">ayangxu</a>的PPT的提点,发现可以用UIWebView实现富文本编辑,而且现今比较知名的app–evernote,似乎就是利用了该技术。这点在我导出笔记文件enex并用编辑器打开发现webkit相关字眼之后也确实得到了验证。大体了解了相关原理,主要是利用了webkit支持HTML5属性contenteditable(就是前段时间讨论的比较热闹的话题–一行代码可把浏览器成变临时编辑器)。与Core Text相比,优点在于节省了需要自己实现HTML到Core Text的繁琐转换;缺点嘛,就是到时出来的HTML片段可能只有在safari浏览器中才表现正常。</p><p>权衡利弊,最终我还是决定用UIWebView来实现富文本编辑。这里先感叹下HTML5的强大。 ^_^</p><h3 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h3><p>参看国外某大大的《<a href="http://ios-blog.co.uk/featured-posts/ios-5-rich-text-editing-series/" target="_blank" rel="noopener">iOS 5 Rich Text Editing Series</a>》,确实给了我很大的帮助。<br>仔细研读了一下其中的代码,我发现实现起来并不是我原本想象中的那么复杂。关键点概述如下:</p><ol><li><p>富文本片段状态的获取和实现都是基于webkit内建的富文本编辑器。</p><p> 编辑器的api可以参考w3c的《<a href="https://dvcs.w3.org/hg/editing/raw-file/tip/editing.html" target="_blank" rel="noopener">HTML Editing APIs</a>》。如果觉得那个文档太复杂,也可以参考Firefox的<a href="https://developer.mozilla.org/en-US/docs/Midas" target="_blank" rel="noopener">Midas</a>的api,如编辑相关的<a href="https://developer.mozilla.org/en-US/docs/Rich-Text_Editing_in_Mozilla#Executing_Commands" target="_blank" rel="noopener">command</a>。还有IE的<a href="http://msdn.microsoft.com/en-us/library/ms533049(v=vs.85).aspx" target="_blank" rel="noopener">Command Identifiers</a>。至于webkit本身似乎相关的文档比较少,我没找到。</p></li><li><p>iOS端设置一个timer,可及时获取光标所在片段的文本状态。</p></li><li>一些交互的操作,比如拖拽图片,光标定位,需要借助javascript,了解selection,range等对象的操作,相关api可以参考《<a href="https://developer.mozilla.org/en-US/docs/Gecko_DOM_Reference" target="_blank" rel="noopener">Gecko DOM Reference</a>》</li></ol><p>注: iOS端是利用UIWebView的<code>stringByEvaluatingJavaScriptFromString</code>方法与javascript交互。</p><p>参看<a href="http://f2e.us/slides/iOS_Rich_Editor/iOS_Rich_Editor.html#slide1" target="_blank" rel="noopener">ayangxu</a>的PPT,他用了较多的hack方法,但目前实际实现过程中,我除了在去除键盘上的黑条用了一点取巧的办法,其他基本上使用的都是sdk的api,具体效果也还是可以接受的,不过还有待完善。</p><p>具体代码参看可以<a href="https://github.com/ddrccw/CCRichTextEditor" target="_blank" rel="noopener">点我</a>。</p><h3 id="遇到的问题"><a href="#遇到的问题" class="headerlink" title="遇到的问题"></a>遇到的问题</h3><p><a href="http://f2e.us/slides/iOS_Rich_Editor/iOS_Rich_Editor.html#slide1" target="_blank" rel="noopener">ayangxu</a>的PPT讨论了不少可能出现的问题(虽然我也没都遇到-。-)。我也顺便补充个小问题好了。</p><h4 id="弹出的UIMenuController"><a href="#弹出的UIMenuController" class="headerlink" title="弹出的UIMenuController"></a>弹出的UIMenuController</h4><p>平常使用时,往往可以通过方法</p><p><code>- (BOOL)canPerformAction:(SEL)action withSender:(id)sender;</code></p><p>返回YES/NO,控制UIMenuController上系统menuItem的出现与否。但是对于UIWebView(iOS>=5),该方法不可行,系统menuItem始终会出现。原因在于决定其响应展示的view是UIWebBrowserView。虽然可以通过category覆盖相应的方法控制,但是该类是私有的,如果app要过审核的话,最好还是不要动它。</p><p>另外,UIWebView在ios5上使用时,弹出的UIMenuController的系统menuItem中没有BIU的选项。iOS6中则有,相当于有个二级菜单包含加粗,斜体和下划线。</p><h2 id="引申"><a href="#引申" class="headerlink" title="引申"></a>引申</h2><h3 id="UIWebView中javascript调试相关"><a href="#UIWebView中javascript调试相关" class="headerlink" title="UIWebView中javascript调试相关"></a>UIWebView中javascript调试相关</h3><p>当要写的javascript代码比较复杂时,尽管可以通过alert的方法调试,但是console的log等方法输出的信息,却是不可见的。还好stackoverflow上有人提供了一个<a href="http://stackoverflow.com/questions/6508313/javascript-console-log-in-an-ios-uiwebview/6508343#6508343" target="_blank" rel="noopener">解决方案</a>,使得log的信息可以输出到xcode的debug终端。</p><h3 id="CoreText-is-better"><a href="#CoreText-is-better" class="headerlink" title="CoreText is better?"></a>CoreText is better?</h3><p>尽管用UIWebView比较容易实现富文本编辑,但实际开发过程中,我觉得对于UIWebView的可控性上感觉还是比较少。DTCoreText的作者就在《<a href="http://www.cocoanetics.com/2011/01/uiwebview-must-die/" target="_blank" rel="noopener">UIWebView must die</a>》中列举UIWebView的种种不是。看了<a href="http://www.cocoanetics.com/2013/02/dtrichtexteditor-1-2/" target="_blank" rel="noopener">DTRichTextEditor</a>的demo,至少可以确定Core Text是可行的。如果有机会的话,不妨了解一下Core Text的实现。</p><p>下面列举一些使用Core Text的富文本展示的项目,希望有所帮助。</p><ul><li><a href="https://github.com/Cocoanetics/DTCoreText" target="_blank" rel="noopener">DTCoreText</a></li><li><a href="https://github.com/enormego/EGOTextView" target="_blank" rel="noopener">EGOTextView</a></li><li><a href="https://github.com/jeremytregunna/JTextView" target="_blank" rel="noopener">JTextView</a></li><li><a href="https://github.com/FuerteInternational/FTCoreText" target="_blank" rel="noopener">FTCoreText</a></li><li><a href="https://github.com/honcheng/RTLabel" target="_blank" rel="noopener">RTLabel</a></li></ul>]]></content>
<summary type="html">
how to make a rich text editor in UIWebView
</summary>
<category term="iOS-dev" scheme="https://ddrccw.github.io/categories/iOS-dev/"/>
<category term="iOS" scheme="https://ddrccw.github.io/tags/iOS/"/>
<category term="UIWebView" scheme="https://ddrccw.github.io/tags/UIWebView/"/>
<category term="javascript" scheme="https://ddrccw.github.io/tags/javascript/"/>
</entry>
<entry>
<title>使用shell脚本build并创建ipa文件</title>
<link href="https://ddrccw.github.io/2013/01/28/2013-01-28-daily-build-and-create-ipa-using-shell-script/"/>
<id>https://ddrccw.github.io/2013/01/28/2013-01-28-daily-build-and-create-ipa-using-shell-script/</id>
<published>2013-01-28T13:06:23.000Z</published>
<updated>2018-08-11T09:43:45.740Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>由于项目引入了敏捷开发,需要每天build出一个ipa供QA测试。此前是使用Xcode先achive出一个文件,再在organizer->achives里发布ipa,一直感觉也没啥不方便的。直到某天,装了InstaSign,突然发现无法用之前的方法codesign自己的ipa(真是自作孽啊T ^ T..),网上有人说是修改了系统自带的codesign和codesign_allocate,重装xcode也没用。不过还好能build出自己项目的app,利用iTune,再创建一个ipa文件。但是这种不得已的办法,对于需要每天都打ipa包的俺来说,实在是太繁琐了。于是就有了利用shell脚本来创建ipa的想法,也就有了此文。</p><h2 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h2><p>放狗搜了一下,发现唐巧的那篇<a href="http://blog.devtang.com/blog/2012/02/16/apply-daily-build-in-ios-project/" title="tangqiao" target="_blank" rel="noopener">《给iOS工程增加Daily Build》</a>比较完整的阐述了daily build的整个过程,这里也就不赘述了。关于我关心的部分,基本思想也很简单:</p><ol><li>利用xcodebuild,build出程序文件\<PRODUCT_NAME>.app。</li><li>再将程序文件\<PRODUCT_NAME>.app里的所有文件,放入Payload文件夹下,利用zip将其打包出一个ipa文件。</li></ol><h3 id="失败的思路"><a href="#失败的思路" class="headerlink" title="失败的思路"></a>失败的思路</h3><p>既然是利用shell,刚开始,我很自然的想到能否在xcode的build phase里添加run script,希望能在build出app后直接利用zip打包。但是经过测试,发现脚本是在<strong>“ProcessProductPackaging(添加embedded.mobileprovision)”和“CodeSign”</strong>之前就开始运行了,显然这个时侯zip的ipa不是有效的ipa。</p><p>那么,能不能直接shell也自己实现<strong>“ProcessProductPackaging(添加embedded.mobileprovision)”和“CodeSign”</strong>呢?</p><p>codesign还好说,但是前者,我实在是搞不透它用了啥内建的工具。</p><p>无奈,此方案作罢。</p><h3 id="真·解决方法"><a href="#真·解决方法" class="headerlink" title="真·解决方法"></a>真·解决方法</h3><p>1)简单的方法</p><p>先利用xcodebuild进行build,因为生成的目录结构是可知的,所以在脚本中给变量设置好相关路径,参考前面介绍的<a href="http://blog.devtang.com/blog/2012/02/16/apply-daily-build-in-ios-project/" title="tangqiao" target="_blank" rel="noopener">那篇文章</a>,定位到相关文件,从而zip出ipa也是理所当然的。</p><p>2)蛋疼的方法</p><p>其实build时,已经有变量可以告诉我们需要的路径,参考<a href="http://developer.apple.com/library/mac/#documentation/developertools/Reference/XcodeBuildSettingRef/0-Introduction/introduction.html" title="xcode build setting" target="_blank" rel="noopener">《xcode build setting reference》</a>,只不过这些build setting的作用范围仅限于build阶段,也就是xcodebuild进程的执行期间。</p><p>不过还好xcodebuild有个-showBuildSettings的参数,可以输出相应configuration的build setting,那么问题的关键就在于如何获取build setting并让其作用于我用于打包的shell脚本。</p><p>注: 因为-showBuildSettings中的build dir是xcode为project生成的唯一的一个目录,其位于<code>~/Library/Developer/Xcode/DerivedData</code>下,而用脚本启动的xcodebuild的build dir是位于脚本所在的当前目录,所以还需要做一些替换,不能获取了直接用。</p><p>我写的shell脚本如下:</p><figure class="highlight bash"><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment"># Created by chenche on 13-1-21.</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line">cnt=1</span><br><span class="line"><span class="keyword">if</span> [ <span class="variable">$#</span> -ne <span class="variable">$cnt</span> ]; <span class="keyword">then</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"error param num, only allow 1 params(case sensitive)!"</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"example:"</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"package <configration> "</span></span><br><span class="line"><span class="built_in">exit</span> -1</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">buildSettings=<span class="string">""</span></span><br><span class="line"></span><br><span class="line">xcodebuild -configuration <span class="variable">$1</span> -target <ProductName> -showBuildSettings | grep --color=never -E <span class="string">'='</span> | awk -F<span class="string">"="</span> -v currentPath=<span class="variable">$PWD</span> <span class="string">'{</span></span><br><span class="line"><span class="string">gsub(/[[:blank:]]*/,"",$1); #去除$1中的所有blank</span></span><br><span class="line"><span class="string">sub(/^[[:blank:]|"]*/,"",$2); #去除头的blank,以及头的双引号</span></span><br><span class="line"><span class="string">sub(/[[:blank:]|"]*$/,"", $2); #去除尾的blank,以及尾的双引号</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">#print "export "$1"=\134\""$2"\134\"";</span></span><br><span class="line"><span class="string">#print $1"=\134\""$2"\134\"";</span></span><br><span class="line"><span class="string">if (tmp == "" && $1=="BUILD_DIR"){</span></span><br><span class="line"><span class="string">tmp=$2;</span></span><br><span class="line"><span class="string">sub(/\/Products$/, "/", tmp);</span></span><br><span class="line"><span class="string">pattern=tmp"[Products|Intermediates]*";</span></span><br><span class="line"><span class="string">#print pattern;</span></span><br><span class="line"><span class="string">#print tmp;</span></span><br><span class="line"><span class="string">}</span></span><br><span class="line"><span class="string">else if (tmp !="") {</span></span><br><span class="line"><span class="string">#如果是给gsub传pattern参数,pattern参数的值无需在两端加"/"</span></span><br><span class="line"><span class="string">#pattern1 = "/Build/[Products|Intermediates]*";</span></span><br><span class="line"><span class="string">#pattern1 = "/Build\\\//";</span></span><br><span class="line"><span class="string">#print pattern1;</span></span><br><span class="line"><span class="string">r = match($2, tmp);</span></span><br><span class="line"><span class="string">if (tmp != "" && r) {</span></span><br><span class="line"><span class="string">#print tmp" $2="$2;</span></span><br><span class="line"><span class="string">gsub(pattern, currentPath"/build", $2);</span></span><br><span class="line"><span class="string">#gsub(/Build\/[Products|Intermediates]*/, "00000000", $2);</span></span><br><span class="line"><span class="string">#print $2;</span></span><br><span class="line"><span class="string">}</span></span><br><span class="line"><span class="string">}</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">print $1"="$2; #key=value</span></span><br><span class="line"><span class="string">}'</span> >buildTmp</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="built_in">read</span> buf</span><br><span class="line"><span class="keyword">do</span></span><br><span class="line"><span class="comment">#echo $c</span></span><br><span class="line">arr[<span class="variable">$c</span>]=<span class="variable">$buf</span></span><br><span class="line"><span class="built_in">let</span> <span class="string">"c = <span class="variable">$c</span> + 1"</span></span><br><span class="line"><span class="keyword">done</span> <buildTmp</span><br><span class="line"></span><br><span class="line">rm -rf buildTmp</span><br><span class="line"></span><br><span class="line"><span class="comment">#只有awk支持关联数组,shell本身的数组不支持,仅支持数字的下标</span></span><br><span class="line"><span class="comment">#echo "array len:" $c</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span>((i=0;i<<span class="variable">$c</span>;i++));</span><br><span class="line"><span class="keyword">do</span></span><br><span class="line">key=<span class="variable">${arr[$i]/=*/}</span></span><br><span class="line">value=<span class="variable">${arr[$i]/*=/}</span></span><br><span class="line"></span><br><span class="line"><span class="comment">#UID is readonly</span></span><br><span class="line"><span class="keyword">if</span> [ <span class="string">"<span class="variable">$key</span>"</span> != <span class="string">"UID"</span> ]; <span class="keyword">then</span></span><br><span class="line"><span class="comment">#if [ -d "$value" ]; then</span></span><br><span class="line"><span class="comment">#echo $key,$value</span></span><br><span class="line"><span class="comment">#fi</span></span><br><span class="line"><span class="built_in">export</span> <span class="variable">$key</span>=<span class="string">"<span class="variable">$value</span>"</span></span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"><span class="keyword">done</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> -e <span class="string">"\033[33;40;1m---------start building <ProductName>...---------\033[0m"</span></span><br><span class="line">xcodebuild -configuration <span class="variable">$1</span> -target <ProductName></span><br><span class="line"><span class="built_in">echo</span> -e <span class="string">"\033[33;40;1m---------build over------------------------------\033[0m"</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> -e <span class="string">"\033[33;40;1m---------start packaging <ProductName>...--------\033[0m"</span></span><br><span class="line"></span><br><span class="line">IPA_PATH=<span class="variable">$SRCROOT</span>/ipa</span><br><span class="line">PAYLOAD_PATH=<span class="variable">$IPA_PATH</span>/Payload</span><br><span class="line"></span><br><span class="line">mkdir -p <span class="variable">$PAYLOAD_PATH</span></span><br><span class="line">cp -r <span class="variable">$TARGET_BUILD_DIR</span>/<span class="variable">$WRAPPER_NAME</span> <span class="variable">$PAYLOAD_PATH</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">cd</span> <span class="variable">$IPA_PATH</span></span><br><span class="line">zip -r <span class="variable">$PRODUCT_NAME</span>.ipa *</span><br><span class="line">mv <span class="variable">$PRODUCT_NAME</span>.ipa <span class="variable">$SRCROOT</span></span><br><span class="line">rm -rf <span class="variable">$IPA_PATH</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> -e <span class="string">"\033[33;40;1m---------<ProductName>.ipa is done.-------------------\033[0m"</span></span><br></pre></td></tr></table></figure><p>上述脚本的不足之处,大概在对于xcodebuild执行失败未作处理,还是会生成一个无效的ipa。虽然xcodebuild执行的成功会输出“** BUILD SUCCEEDED **”,但总感觉单纯的基于这点的判断有点不靠谱。故还是作罢了,人工判断好了。</p><h2 id="引申"><a href="#引申" class="headerlink" title="引申"></a>引申</h2><p>写脚本的过程中,我也碰到过一些问题,汇总如下:</p><ol><li><p>普通数组和关联数组</p><p> 所谓普通数组,下标是数字;关联数组类似字典,下标可以是数字或字符串。网上搜了不少资料,都说shell支持关联数组,但是实际写脚本的过程,发现mac下的bash还是只支持索引数组,awk命令倒是支持关联数组。</p><p> 另外,可以man bash,发现相关内容,也证实了如上观点:</p><blockquote><p>An array is created automatically if any variable is assigned to using the syntax name[subscript]=value. <strong>The subscript is treated as an arithmetic expression that must evaluate to a number greater than or equal to zero.</strong></p></blockquote></li><li><p>环境变量</p><p> 环境变量只能从父进程到子进程单向继承。也就是说,子进程的环境变量不会影响父进程的。</p><p> 基于1、2,也就说明无法利用awk export相关build setting,影响打包的shell脚本进程。</p></li><li><p>脚本和awk的信息交互</p><p> a 脚本->awk</p><ul><li>利用export,实现环境变量的单向继承。</li><li><p>awk有个-v的参数,可以传递变量</p><p>b awk->脚本</p></li><li><p>eval, 使用起来有点像javascript中的eval</p></li><li>导出信息到临时文件,再利用临时文件获取相关信息</li></ul></li></ol><p>因为build setting里的值情况比较复杂,最终我还是选择了用临时文件的方式获取awk过滤出来的build setting信息,再在shell脚本中export。最终,这样就可以利用build setting的相关值了。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>好吧,其实我是在吐槽自己花了老长一段时间憋出shell脚本的艰辛历程。。。虽然有点小题大做,但好歹是复习巩固了一下shell的相关知识,也算没白费劲~~~~</p>]]></content>
<summary type="html">
使用shell脚本build并创建ipa文件
</summary>
<category term="iOS-dev" scheme="https://ddrccw.github.io/categories/iOS-dev/"/>
<category term="iOS" scheme="https://ddrccw.github.io/tags/iOS/"/>
<category term="shell" scheme="https://ddrccw.github.io/tags/shell/"/>
<category term="ipa" scheme="https://ddrccw.github.io/tags/ipa/"/>
<category term="daily build" scheme="https://ddrccw.github.io/tags/daily-build/"/>
<category term="build setting" scheme="https://ddrccw.github.io/tags/build-setting/"/>
</entry>
<entry>
<title>关于NSDate的tips</title>
<link href="https://ddrccw.github.io/2012/12/30/2012-12-30-tip-on-date/"/>
<id>https://ddrccw.github.io/2012/12/30/2012-12-30-tip-on-date/</id>
<published>2012-12-30T11:10:17.000Z</published>
<updated>2018-08-11T09:43:45.739Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最近看了一些开源Calendar的实现,里面有不少关于NSDate与NSCalendar、 NSDateComponents、 NSDateFormatter等对象的转换,看着看着又差点把自己绕晕了。想起过去曾解决过的界面显示的时间与服务器端传过来的时间不一致的问题,看来有必要做个了断了。嘿嘿。</p><h2 id="正题"><a href="#正题" class="headerlink" title="正题"></a>正题</h2><h3 id="一些基本概念"><a href="#一些基本概念" class="headerlink" title="一些基本概念"></a>一些基本概念</h3><ul><li><p>NSDate是基于<a href="">GMT</a>(<a href="">UTC</a>),参考文档</p><blockquote><p>The sole primitive method of NSDate, timeIntervalSinceReferenceDate, provides the basis for all the other methods in the NSDate interface. This method returns a time value relative to an absolute reference date—the first instant of 1 January 2001, GMT.</p></blockquote></li><li><p>格林尼治标准时间(<a href="http://zh.wikipedia.org/wiki/GMT" target="_blank" rel="noopener">GMT</a>)与 世界标准时间(<a href="http://zh.wikipedia.org/wiki/UTC" target="_blank" rel="noopener">UTC</a>)</p><p> 简单的理解,两者都是世界时间计算的标准,区别是精度上,UTC更精确些。因而UTC现在也多用来代替GMT。</p></li><li><p>区域设置(<a href="http://zh.wikipedia.org/wiki/区域设置" target="_blank" rel="noopener">locale</a>)与 时区(<a href="http://zh.wikipedia.org/wiki/时区" target="_blank" rel="noopener">timezone</a>)</p><p> 个人理解,区域设置关注的是界面上的格式的显示,包含日期、货币等格式。时区则是使用同一时间的具体区域。两者千万不能等同,具体见后面说明。</p><p> ps:具体到开发过程中,在ios设备上的设置app,通用->多语言环境->区域格式(不是语言),可以更改locale的配置(影响 [NSLoale currentLocale]),通用->日期与时间,可以设置具体的时区,默认是自动设置(影响 [NSTimeZone systemTimeZone])。另外,ios模拟器上只能设置区域格式,时区则始终是自动设置的。</p></li></ul><h3 id="结合例子说明"><a href="#结合例子说明" class="headerlink" title="结合例子说明"></a>结合例子说明</h3><h4 id="例子"><a href="#例子" class="headerlink" title="例子"></a>例子</h4><p>代码如下:</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><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"><span class="built_in">NSLog</span>(<span class="string">@"current locale(%@) and timezone(%@)"</span>, [[<span class="built_in">NSLocale</span> currentLocale] localeIdentifier], [<span class="built_in">NSTimeZone</span> systemTimeZone]);</span><br><span class="line"><span class="built_in">NSCalendar</span> *calendar = [[[<span class="built_in">NSCalendar</span> alloc] initWithCalendarIdentifier:<span class="built_in">NSGregorianCalendar</span>] autorelease];</span><br><span class="line"><span class="built_in">NSDate</span> *date = [<span class="built_in">NSDate</span> date];</span><br><span class="line"><span class="built_in">NSLog</span>(<span class="string">@"defaut description:%@"</span>, date); <span class="comment">//因为date是GMT,显示为当前时区时间-8h</span></span><br><span class="line"><span class="built_in">NSLog</span>(<span class="string">@"current locale description:%@"</span>, [date descriptionWithLocale:[<span class="built_in">NSLocale</span> currentLocale]]); <span class="comment">//显示当前时区时间</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//calendar是基于当前时区的,date是GMT,comps是返回date+8的当前时区的时间</span></span><br><span class="line"><span class="built_in">NSDateComponents</span> *comps = [calendar components:(<span class="built_in">NSYearCalendarUnit</span> |</span><br><span class="line"> <span class="built_in">NSMonthCalendarUnit</span> |</span><br><span class="line"> <span class="built_in">NSDayCalendarUnit</span> |</span><br><span class="line"> <span class="built_in">NSHourCalendarUnit</span> |</span><br><span class="line"> <span class="built_in">NSMinuteCalendarUnit</span> |</span><br><span class="line"> <span class="built_in">NSSecondCalendarUnit</span>)</span><br><span class="line"> fromDate:date];</span><br><span class="line">comps.timeZone = [<span class="built_in">NSTimeZone</span> timeZoneWithAbbreviation:<span class="string">@"GMT"</span>];</span><br><span class="line"><span class="built_in">NSLog</span>(<span class="string">@"timezone=%@, comps=%@"</span>, comps.timeZone, comps);</span><br><span class="line"><span class="comment">//calendar是基于当前时区的,返回的date是GMT,故显示的时间减去8h</span></span><br><span class="line"><span class="built_in">NSLog</span>(<span class="string">@"calendar date default:%@"</span>, [calendar dateFromComponents:comps]);</span><br><span class="line"></span><br><span class="line"><span class="built_in">NSString</span> *dateString = <span class="string">@"2012-12-28 18:00:00"</span>; </span><br><span class="line"><span class="built_in">NSDateFormatter</span> *formatter = [[[<span class="built_in">NSDateFormatter</span> alloc] init] autorelease];</span><br><span class="line">formatter.locale = [<span class="built_in">NSLocale</span> currentLocale];</span><br><span class="line">[formatter setTimeStyle:kCFDateFormatterShortStyle]; <span class="comment">//影响时分秒格式</span></span><br><span class="line">[formatter setDateStyle:<span class="built_in">NSDateFormatterLongStyle</span>]; <span class="comment">//影响年月日格式</span></span><br><span class="line"><span class="built_in">NSLog</span>(<span class="string">@"%@"</span>, [formatter stringFromDate:date]);</span><br><span class="line"></span><br><span class="line">formatter.dateFormat = <span class="string">@"yyyy-MM-dd HH:mm:ss"</span>;</span><br><span class="line"><span class="comment">//formatter的默认timezone是当前时区的,Date是基于GMT的,显示的是当前时区的</span></span><br><span class="line"><span class="built_in">NSLog</span>(<span class="string">@"default locale(%@) timezone(%@) formatter(date to string):%@"</span>,</span><br><span class="line"> formatter.locale.localeIdentifier,</span><br><span class="line"> formatter.timeZone,</span><br><span class="line"> [formatter stringFromDate:date]);</span><br><span class="line"><span class="comment">//formatter的默认timezone是当前时区的,dateString则对应时区理解为当前时区的,Date是基于GMT的,故显示的时间减去8h</span></span><br><span class="line"><span class="built_in">NSLog</span>(<span class="string">@"default locale(%@) timezone(%@) formatter(string to date):%@"</span>,</span><br><span class="line"> formatter.locale.localeIdentifier,</span><br><span class="line"> formatter.timeZone,</span><br><span class="line"> [formatter dateFromString:dateString]);</span><br><span class="line"></span><br><span class="line">formatter.timeZone = [<span class="built_in">NSTimeZone</span> timeZoneWithAbbreviation:<span class="string">@"GMT"</span>]; <span class="comment">//GMT ~ UTC</span></span><br><span class="line"><span class="comment">//formatter的timezone是GMT,date是GMT,故显示的是GMT</span></span><br><span class="line"><span class="built_in">NSLog</span>(<span class="string">@"customize locale(%@) timezone(%@) formatter(date to string):%@"</span>,</span><br><span class="line"> formatter.locale.localeIdentifier,</span><br><span class="line"> formatter.timeZone,</span><br><span class="line"> [formatter stringFromDate:date]);</span><br><span class="line"><span class="comment">//formatter的timezone是GMT,dateString则对应时区理解为GMT, date是GMT,故显示的是GMT的</span></span><br><span class="line"><span class="built_in">NSLog</span>(<span class="string">@"customize locale(%@) timezone(%@) formatter(string to date):%@"</span>,</span><br><span class="line"> formatter.locale.localeIdentifier,</span><br><span class="line"> formatter.timeZone,</span><br><span class="line"> [formatter dateFromString:dateString]);</span><br></pre></td></tr></table></figure><p>结果如下:<br><figure class="highlight bash"><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">2012-12-31 14:15:06.900 DateTest[1281:707] current locale(zh_CN) and timezone(Asia/Shanghai (CST (China)) offset 28800)</span><br><span class="line">2012-12-31 14:15:06.903 DateTest[1281:707] defaut description:2012-12-31 06:15:06 +0000</span><br><span class="line">2012-12-31 14:15:06.906 DateTest[1281:707] current locale description:2012年12月31日星期一 中国标准时间下午2时15分06秒</span><br><span class="line">2012-12-31 14:15:06.910 DateTest[1281:707] timezone=GMT (GMT) offset 0, comps=<NSDateComponents: 0xee664f0></span><br><span class="line"> TimeZone: GMT (GMT) offset 0</span><br><span class="line"> Calendar Year: 2012</span><br><span class="line"> Month: 12</span><br><span class="line"> Day: 31</span><br><span class="line"> Hour: 14</span><br><span class="line"> Minute: 15</span><br><span class="line"> Second: 6</span><br><span class="line">2012-12-31 14:15:06.911 DateTest[1281:707] calendar date default:2012-12-31 14:15:06 +0000</span><br><span class="line">2012-12-31 14:15:06.917 DateTest[1281:707] 2012年12月31日 下午2:15</span><br><span class="line">2012-12-31 14:15:06.919 DateTest[1281:707] default locale(zh_CN) timezone(Asia/Shanghai (CST (China)) offset 28800) formatter(date to string):2012-12-31 14:15:06</span><br><span class="line">2012-12-31 14:15:06.921 DateTest[1281:707] default locale(zh_CN) timezone(Asia/Shanghai (CST (China)) offset 28800) formatter(string to date):2012-12-28 10:00:00 +0000</span><br></pre></td></tr></table></figure></p><h3 id="关键点"><a href="#关键点" class="headerlink" title="关键点"></a>关键点</h3><ol><li>NSDate始终是基于GMT的,与系统时区的设置无关。</li><li>NSDateComponents只是某个具体时间点(概念意义上的时间,不是NSDate型数据)的具体部分的组成,同样是与系统时区的设置无关。</li><li>在NSDate与其他类型数据(如NSDateComponents,NSString)的转换过程中,负责转换的实例(如NSCalendar,NSDateFormatter)的timezone,默认值会受系统时区的设置影响,同时该属性是用来理解要转换成NSDate型数据的源数据的时区或NSDate型数据要转换成的目标数据的时区。比如 date to string,最终date会转换为NSDateformatter的timezone对应的时间string。</li><li>尽管NSDate可以通过locale,打印相应的timezone的时间,但不代表locale和timezone是有直接关系的。上述例子NSDateFormatter的使用就说明了这点,timezone的设置才是NSDate型数据是否需要加减时间的关键。但是locale的设置会影响显示,比如fomatter格式说明符包含”eeee”,locale为zh_CN,则会显示”星期一”。</li></ol><h2 id="引申"><a href="#引申" class="headerlink" title="引申"></a>引申</h2><ol><li><p>在与后台的交互过程中,传递时间参数是很常见的。时间参数所基于的时区必须搞清楚,还有对于不同的时间数据存储类型,要小心因为时区的设置不当而导致时间计算出错,而在展示上出现偏差。</p></li><li><p>c/c++对于时间的处理虽然比较繁琐,但也相对直白一些,因为时间的处理基本集中在struct tm 和 time_t比较底层的数据类型上。</p></li></ol><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><p>1)<a href="http://zh.wikipedia.org/wiki/GMT" target="_blank" rel="noopener">GMT</a></p><p>2)<a href="http://zh.wikipedia.org/wiki/UTC" target="_blank" rel="noopener">UTC</a></p><p>3)<a href="http://zh.wikipedia.org/wiki/区域设置" target="_blank" rel="noopener">locale</a></p><p>4)<a href="http://zh.wikipedia.org/wiki/时区" target="_blank" rel="noopener">timezone</a></p><p>5)<a href="http://www.cplusplus.com/reference/ctime/" target="_blank" rel="noopener">http://www.cplusplus.com/reference/ctime/</a></p><p>6)<a href="http://userguide.icu-project.org/formatparse/datetime" target="_blank" rel="noopener">Formatting Dates and Times</a></p><p>7)<a href="http://developer.apple.com/library/ios/#qa/qa1480/_index.html" target="_blank" rel="noopener">NSDateFormatter and Internet Dates</a></p>]]></content>
<summary type="html">
tip on date
</summary>
<category term="iOS-dev" scheme="https://ddrccw.github.io/categories/iOS-dev/"/>
<category term="iOS" scheme="https://ddrccw.github.io/tags/iOS/"/>
<category term="NSDate" scheme="https://ddrccw.github.io/tags/NSDate/"/>
<category term="NSDateFormatter" scheme="https://ddrccw.github.io/tags/NSDateFormatter/"/>
<category term="UTC" scheme="https://ddrccw.github.io/tags/UTC/"/>
<category term="GMT" scheme="https://ddrccw.github.io/tags/GMT/"/>
</entry>
<entry>
<title>升级app到ios6</title>
<link href="https://ddrccw.github.io/2012/12/02/2012-12-02-upgrade-to-ios6/"/>
<id>https://ddrccw.github.io/2012/12/02/2012-12-02-upgrade-to-ios6/</id>
<published>2012-12-02T14:45:29.000Z</published>
<updated>2018-08-11T14:02:42.671Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>手头上的项目开发时原本跑的板子是ipad2(ipad5.1.1),但考虑到客户那边到时会购买新的ipad,势必要让现阶段的代码能够在ios6上运行,故花了点时间做了一下适配工作。已下讨论到的问题仅在我的适配过程中遇到,并不涵盖升级app到ios6的全部问题。</p><h2 id="正题"><a href="#正题" class="headerlink" title="正题"></a>正题</h2><h3 id="旋转"><a href="#旋转" class="headerlink" title="旋转"></a>旋转</h3><p>参考<a href="https://developer.apple.com/library/ios/#releasenotes/General/RN-iOSSDK-6_0/_index.html" title="RN-iOSSDK-6_0" target="_blank" rel="noopener">RN-iOSSDK-6_0</a>,在<ios6的版本中,UIViewController类中提供<code>shouldAutorotateToInterfaceOrientation:</code>的方法告诉系统实现的VC支持什么方向的旋转。但在ios6中,旋转支持的判断则不会涉及到开发过程中的每个VC,责任更多的推到了上游。</p><p>一方面,系统会从app提供的info.plist文件和app的delegate类的<code>application:supportedInterfaceOrientationsForWindow:</code>方法中获取旋转支持的信息。当然,后者优先级大于前者。</p><p>另一方面,系统会从<strong>root</strong>或<strong>top-most full-screen</strong>的VC的<code>supportedInterfaceOrientationsForWindow:</code>和 <code>shouldAutorotate</code> 方法获取旋转支持的信息。所谓<strong>root</strong>,就是一定要在app delegate类中设置rootViewController。<strong>top-most full-screen</strong>则很有迷惑性,你可能会想当然的以为是自己在屏幕上看到的app当前全屏状态的那个VC。其实不然,如果你用了UINavigationController或其他类似的容器类时,那些容器类才是真正的<strong>top-most full-screen</strong>的VC。</p><p>实际适配过程中,一是我的UINavigationController本身也是位于其他自定义的容器类A中,二是实际主要展示的界面内容却又是在UINavigationController的子VC中。所以为了让app支持旋转,我需要让系统知道UINavigationController的子VC旋转支持的方向。有了思路,剩下的就好办。分别实现容器类A,UINavigationController类,显示内容的VC类的<code>supportedInterfaceOrientationsForWindow:</code> 和 <code>shouldAutorotate</code> 方法。虽然系统只会问容器类A,但我只要从容器类A一级一级往下询问即可。</p><p>注:UINavigationController需要子类化或一个Category类才能重写那两个方法。</p><h3 id="导航条自定义背景"><a href="#导航条自定义背景" class="headerlink" title="导航条自定义背景"></a>导航条自定义背景</h3><p>由于项目设计需要,同一个navigationController的不同层次的导航条背景有些许不同,并且设计师提供的只是一小段图片(需要将图片平铺)。</p><p>切换背景倒是没什么问题,无非是在viewWillAppear中,利用self.navigationController.navigationBar(不是[UINavigationBar appearance])的<code>setBackgroundImage:forBarMetrics:</code>方法更改。</p><p>但是关键的是在平铺图片的时候出了点麻烦。。。ios5中没有任何问题,在demo程序中如图1,</p><p><img src="/images/upgrade-to-ios6/2.1.png" alt="alt normal" title="正常情况"></p><p>然而在ios6中出现了诡异的问题,导航条背景出错了,如图2,</p><p><img src="/images/upgrade-to-ios6/2.2.png" alt="alt weird" title="诡异情况"></p><p>刚开始还以为是代码错了,后来经过多番排查测试,才发现问题出在图片上。一般来说设计师出的图如果不是透明的,会在保存png图片时把透明度一项去除掉,这样的图相比使用了alpha通道的图会小不少。ios6估计底层改了一些代码,由于给的图比较小,又没有alpha通道,导致出现了图2的情况。</p><p>解决方法:给图片添加上alpha通道或把可以平铺的图片尺寸加大即可。</p><p>ps:<br>起初,还怀疑是代码问题,顺便试了一下[UINavigationBar appearance]的<code>setBackgroundImage:forBarMetrics:</code>方法,该方法是修改全局的导航条背景,并且需要在viewDidLoad之前使用,而在viewDidLoad中使用时,不影响当前的导航条。 </p><h3 id="handle-low-memory-warning"><a href="#handle-low-memory-warning" class="headerlink" title="handle low memory warning"></a>handle low memory warning</h3><p><iOS6</p><p>当内存不够时,系统会先调用didReceiveMemoryWarning,其中调用super的该方法会让系统根据当前view的情况(比如是否在屏幕上),决定释放VC的view。如果要释放view,便会依次调用viewWillUnload和viewDidUnload。一般来说,可以在viewWillUnload和viewDidUnload中释放与view相关的对象(这些对象往往可以在viewDidLoad中重新构建)。而因为在vc的view仍在屏幕上时,仍可能接收low memory warning,所以不在didReceiveMemoryWarning释放view相关的对象,但可以释放一些不重要的数据结构。</p><p>>=iOS6</p><p>当内存不够时,系统将只会调用didReceiveMemoryWarning。根据<a href="http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/ViewLoadingandUnloading/ViewLoadingandUnloading.html#//apple_ref/doc/uid/TP40007457-CH10-SW1" title="view controller programming guide for iOS" target="_blank" rel="noopener">文档</a></p><blockquote></blockquote><p>The memory used by a view to draw itself onscreen is potentially quite large. However, the system automatically releases these expensive resources when the view is not attached to a window. The remaining memory used by most views is small enough that it is not worth it for the system to automatically purge and recreate the view hierarchy.</p><p>view相关对象的内存管理似乎交给了系统,而didReceiveMemoryWarning依然重点关注释放那些不重要的数据结构。当然,想要显式释放view相关对象也是可以的,请参考</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></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)didReceiveMemoryWarning</span><br><span class="line">{</span><br><span class="line"> [<span class="keyword">super</span> didReceiveMemoryWarning];</span><br><span class="line"> <span class="comment">// Add code to clean up any of your own resources that are no longer necessary.</span></span><br><span class="line"> <span class="keyword">if</span> ([<span class="keyword">self</span>.view window] == <span class="literal">nil</span>)</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// Add code to preserve data stored in the views that might be</span></span><br><span class="line"> <span class="comment">// needed later.</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// Add code to clean up other strong references to the view in</span></span><br><span class="line"> <span class="comment">// the view hierarchy.</span></span><br><span class="line"> <span class="keyword">self</span>.view = <span class="literal">nil</span>;</span><br><span class="line"> <span class="keyword">self</span>.otherView = <span class="literal">nil</span>; <span class="comment">//add by me</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>适配过程,只要参考上面的说明更改即可。我的app内存要求不高,所以暂时不采用显式管理的方法。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>总的来说,目前还算比较顺利的升级了app。另外,photoshop知识有待加强,有空需要补一补>_<</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><p>1) <a href="https://developer.apple.com/library/ios/#releasenotes/General/RN-iOSSDK-6_0/_index.html" title="RN-iOSSDK-6_0" target="_blank" rel="noopener">RN-iOSSDK-6_0</a> </p><p>2) <a href="http://stackoverflow.com/questions/8257556/ios-5-curious-about-uiappearance/" target="_blank" rel="noopener">http://stackoverflow.com/questions/8257556/ios-5-curious-about-uiappearance/</a></p><p>3) <a href="http://pan.baidu.com/share/link?shareid=154803&uk=1678482707/" title="navi-demo" target="_blank" rel="noopener">navi-demo</a></p><p>4) WWDC 2012 Session 236 - The Evolution of View Controllers on iOS</p><p>5) <a href="http://stackoverflow.com/questions/4354332/didreceivememorywarning-and-viewdidunload" target="_blank" rel="noopener">http://stackoverflow.com/questions/4354332/didreceivememorywarning-and-viewdidunload</a></p><p>6) <a href="http://stackoverflow.com/questions/5069978/didreceivememorywarning-viewdidunload-and-dealloc" target="_blank" rel="noopener">http://stackoverflow.com/questions/5069978/didreceivememorywarning-viewdidunload-and-dealloc</a></p>]]></content>
<summary type="html">
手头上的项目开发时原本跑的板子是ipad2(ipad5.1.1),但考虑到客户那边到时会购买新的ipad,势必要让现阶段的代码能够在ios6上运行,故花了点时间做了一下适配工作。已下讨论到的问题仅在我的适配过程中遇到,并不涵盖升级app到ios6的全部问题。
</summary>
<category term="iOS-dev" scheme="https://ddrccw.github.io/categories/iOS-dev/"/>
<category term="ios6" scheme="https://ddrccw.github.io/tags/ios6/"/>
<category term="upgrade" scheme="https://ddrccw.github.io/tags/upgrade/"/>
<category term="UINavigationBar" scheme="https://ddrccw.github.io/tags/UINavigationBar/"/>
</entry>
<entry>
<title>Google Objective-C Style Guide学习笔记</title>
<link href="https://ddrccw.github.io/2012/11/25/2012-11-25-learn-from-google-objective-c-style-guide/"/>
<id>https://ddrccw.github.io/2012/11/25/2012-11-25-learn-from-google-objective-c-style-guide/</id>
<published>2012-11-25T11:10:53.000Z</published>
<updated>2018-08-11T09:43:45.738Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>之前拜读了《clean code》一书,在它的前言里看到了这样一句经典的话:</p><p><code>The only valid measurement of code quality: WTF/min</code></p><p>译:<code>衡量代码质量的唯一有效标准:WTF/min</code></p><p>当时顿时就笑喷了,但静下心来一想,虽然可能有些夸张,但是也确实有道理。回想以前接手过的历史遗留代码,有看过比较厉害的前辈,纵然不使用注释,但代码看起来却也让人十分清爽。有的同一个项目但是因为有多人开发,代码风格迥异。更有甚者,即便是同一个人写的几段代码,竟然也能变异出几种风格,看的我甚是蛋疼。。。<br>虽然说像geek一样编码,可能是一件十分cool的事,但落实到现实团队合作中,我觉得还是要统一风格才好。毕竟,一个良好的code style也是一个程序员的基本功吧。至少,我不想让下一任接我活的兄弟因此而”记恨于我” ^_^ ~~</p><h2 id="正题"><a href="#正题" class="headerlink" title="正题"></a>正题</h2><p>某天刷weibo发现<a href="http://weibo.com/u/2093492691" target="_blank" rel="noopener">程序员的那些事</a>推荐<a href="http://code.google.com/p/google-styleguide/" target="_blank" rel="noopener">《google code style》</a>,遂花点时间学习了一下 <a href="http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml" target="_blank" rel="noopener">《C++ Style Guide》</a>,<br><a href="http://google-styleguide.googlecode.com/svn/trunk/objcguide.xml" target="_blank" rel="noopener">《Objective-C Style Guide》</a>。</p><p><strong>已下仅记录部分参考借鉴的知识点</strong></p><ol><li><p>缩进,2个空格 ;行宽,80字节</p><p> 考虑到obj-c较长的命名规则,并且实际开发的代码可能来着iMac或MBP,为了照顾MBP的小屏幕,这个还是有必要的。</p></li><li><p>文件名前缀</p><p> 一般应用程序的代码,不需要前缀,但是在多个app中会共享的代码,还是需要前缀。</p></li><li><p>混合编程时</p><p> @implementation中采用obj-c风格;实现c++类方法时采用c++风格。</p></li><li><p>实例变量</p><p> 除了配合@property使用的实例变量延续开头带下划线的风格,其他的均采用结尾带下划线的风格。</p></li><li><p>对象所有关系</p><p> CoreFoundation,c++和非obj-c对象的实例变量的指针用<code>__strong</code>和<code>__weak</code>表示是否需要retain。CoreFoundation和非obj-c对象无论是否使用arc或gc,都应该显式地进行内存管理。</p></li><li><p>保持api的简洁性</p><p> obj-c的所有方法都是public,尽管利用private category来写method可以避免在public header暴露所有方法,但并不会让method真的变得private</p></li><li><p>头文件引入</p><p> obj-c/obj-c++ header用#import,标准c、c++ header用#include(为防止重复引用,设置#define guard,其命名规则为<code><project>_<path>_<filename>_H_</code>)</p></li><li><p>避免在init和dealloc中使用访问器</p><p> 因为子类实例可能在init和dealloc时存在状态的不一致</p></li><li><p>dealloc中实例变量的顺序和@interface中的顺序一致(lz是先private,再public)</p></li><li><p>避免抛出obj-c的异常</p><p>避免,但要随时准备catch第三方或系统调用抛出的异常。当写obj-c++代码中要抛出obj-c的异常时,代码中基于栈的对象不会调用析构函数,所以在obj-c++代码中要抛出obj-c异常,则不应该在@try,@catch,@final的block中包含基于栈的对象。建议在obj-c++中如果一定要抛异常,使用c++的异常。</p><p>关于异常的使用,google似乎是从项目之间的整合考虑,可以参考</p><p><a href="http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Exceptions" target="_blank" rel="noopener">http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Exceptions</a></p></li><li><p>BOOL 陷阱–这一段估计google自己没更新</p><p>文档中说BOOL在obj-c中是unsigned char,但在<obj/obj.h>中发现BOOL应该是signed char,要指正一下。</p><p>BOOL类型的值可以是除了YES(1)和NO(0)以外的其他值。</p><ul><li><p>根据<a href="http://c0x.coding-guidelines.com/6.3.1.2.html" target="_blank" rel="noopener">c99 Std 6.3.1.2</a> </p><p> <code>When any scalar value is converted to _Bool, the result is 0 if the value compares equal to 0; otherwise, the result is 1</code></p><p> 故,BOOL与_Bool/bool可以安全转换</p></li><li><p>Boolean是unsigned char,boolean_t一般为int,均不可于BOOL安全转换</p></li></ul><p>基于上述两点</p><ul><li>逻辑运算符(&&,||,!)运算BOOL是合法的</li><li>函数返回的值也必须是可以安全转换为BOOL类型的(尤其是在转换数组大小,指针,位运算结果时要注意)</li><li>不要直接和YES比较BOOL类型的值</li></ul></li></ol>]]></content>
<summary type="html">
learn from Google Objective-C Style Guide
</summary>
<category term="iOS-dev" scheme="https://ddrccw.github.io/categories/iOS-dev/"/>
<category term="iOS" scheme="https://ddrccw.github.io/tags/iOS/"/>
<category term="Objective-c" scheme="https://ddrccw.github.io/tags/Objective-c/"/>
<category term="Google" scheme="https://ddrccw.github.io/tags/Google/"/>
<category term="code-style" scheme="https://ddrccw.github.io/tags/code-style/"/>
</entry>
<entry>
<title>apple products comparison</title>
<link href="https://ddrccw.github.io/2012/10/08/2012-10-08-apple-products-comparison/"/>
<id>https://ddrccw.github.io/2012/10/08/2012-10-08-apple-products-comparison/</id>
<published>2012-10-08T10:18:00.000Z</published>
<updated>2018-08-11T09:43:45.738Z</updated>
<content type="html"><![CDATA[<p>不知不觉,我也差不多搞了半年左右的iOS开发了。虽然大多数接触的是软件层面上的事,apple本身的产品确实也用着比较爽,但每每有人问起apple产品具体的硬件特点,我却真的答不出个所以然。。。囧rz。尽管硬件看似与软件开发没啥关系,但细细一想,硬件也算平台的一个卖点。一方面,硬件的推广势必增加潜在的软件用户,另一方面,硬件自身的提升与改变必然也会影响软件的性能和开发。也正因此,我决定要花点时间来对apple的移动端产品做个了解。以下的数据资料主要是从苹果官网和维基百科上扒下来的,不全,但也应该能够有个大概的了解。</p><p>update: 2013.12.8</p><p>颜色只标示了比较重要的变化:字体颜色表示与前代相比有变化,背景颜色间隔变化</p><h2 id="iphone"><a href="#iphone" class="headerlink" title="iphone"></a>iphone</h2><p>详见:<a href="http://en.wikipedia.org/wiki/Iphone" target="_blank" rel="noopener">http://en.wikipedia.org/wiki/Iphone</a></p><table><thead><tr><th></th><th>iphone 3GS</th><th>iphone 4</th><th>iPhone 4s</th><th>iphone 5</th></tr></thead><tbody><tr><td>上市时间</td><td>2009.7.19</td><td>2010.7.24</td><td>2011.10.14</td><td>2012.9.21</td></tr><tr><td>处理器</td><td><em style="color:red"> ARM Cortex-A8 (600MHZ)</em></td><td><em style="color:green">A4 芯片(800MHz)</em></td><td><em style="color:blue">双核 Apple A5 芯片(800MHz x 2)</em></td><td><em style="color:red">双核 Apple A6(800mhz~1.2Gz)+3核GPU</em></td></tr><tr><td>iOS</td><td>ios3,可升上 iOS 6</td><td>ios4,可升上 iOS 6</td><td>ios5,可升上 iOS 6</td><td>预载 iOS 6</td></tr><tr><td>RAM</td><td>256MB</td><td><em style="color:green">512MB</em></td><td>512MB</td><td><em style="color:red"> 1G </em></td></tr><tr><td>屏幕</td><td>{::nomarkdown} <li> 3.5 英寸 (对角线) Multi-Touch 触控宽屏幕 </li><li> 480 x 320 分辨率 163 ppi {:/}</li></td><td>{::nomarkdown} <li> 3.5 英寸(对角线) Retina 显示屏 </li><li style="color:green"> 960 x 640 分辨率 326 ppi {:/}</li></td><td>{::nomarkdown} <li> 3.5 英寸(对角线) Retina 显示屏 </li><li> 960 x 640 分辨率 326 ppi {:/}</li></td><td>{::nomarkdown} <li style="color:red"> 4 英寸(对角线) Retina 显示屏 </li><li style="color:red"> 1136 x 640 分辨率 326 ppi {:/}</li></td></tr><tr><td>主相机</td><td>{::nomarkdown} <li> 300 万像素 </li><li> 轻点对焦 </li><li> 照片和视频地理标记功能 {:/}</li></td><td>{::nomarkdown} <li style="color:green"> 500 万像素 </li><li> 轻点对焦 </li><li> 自动对焦 </li><li> LED 闪光灯 </li><li> 背部照度传感器 </li><li> 四鏡片鏡頭 </li><li> f/2.8光圈 {:/}</li></td><td>{::nomarkdown} <li style="color:blue"> 800 万像素 </li><li> 轻点对焦 </li><li> 自动对焦 </li><li> LED 闪光灯 </li><li> 背部照度传感器 </li><li> 五鏡片鏡頭 </li><li> f/2.4光圈 </li><li> 面部检测 </li><li> 混合红外线滤镜 </li><li> 全景模式 {:/}</li></td><td>{::nomarkdown} <li> 800 万像素 </li><li> 轻点对焦 </li><li> 自动对焦 </li><li> LED 闪光灯 </li><li> 背部照度传感器 </li><li> 五鏡片鏡頭 </li><li> f/2.4光圈 </li><li> 面部检测 </li><li> 混合红外线滤镜 </li><li> 全景模式 {:/}</li></td></tr><tr><td>前置相机</td><td>-</td><td>{::nomarkdown} <li> VGA 分辨率照片 </li><li> VGA 分辨率视频 {:/}</li></td><td>{::nomarkdown} <li> VGA 分辨率照片 </li><li> VGA 分辨率视频 {:/}</li></td><td>{::nomarkdown} <li style="color:red"> 120 万像素画质照片 </li><li> 720p HD 高清视频 </li><li> 背部照度传感器 {:/}</li></td></tr><tr><td>录像</td><td>高达每秒 30 帧 (fps) 带声音的 VGA 视频</td><td>{::nomarkdown} <li style="color:green"> 720p HD 高清摄像 </li><li> 每秒 30 帧 </li><li> 摄制过程中轻点对焦 </li><li> 视频防抖动 {:/}</li></td><td>{::nomarkdown} <li style="color:blue"> 1080p HD 高清摄像 </li><li> 每秒 30 帧 </li><li> 摄制过程中轻点对焦 </li><li> 视频防抖动 {:/}</li></td><td>{::nomarkdown} <li> 1080p HD 高清摄像 </li><li> 每秒 30 帧 </li><li> 摄制过程中轻点对焦 </li><li> 视频防抖动 </li><li> 摄像过程中拍摄静态照片 </li><li> 面部检测 {:/}</li></td></tr><tr><td>网络</td><td>{::nomarkdown} <li> UMTS/HSDPA (850, 1900, 2100 MHz) </li><li> GSM/EDGE (850, 900, 1800, 1900 MHz) </li><li> 802.11b/g WLAN </li><li> 蓝牙 2.1 + EDR 无线技术 </li><li> 辅助全球卫星定位系统 {:/}</li></td><td>{::nomarkdown} <li> GSM 机型:GSM/EDGE UMTS/HSPA </li><li> CDMA 机型:CDMA EV-DO Rev. A </li><li> WLAN (802.11b/g/n;使用 802.11n 时工作在 2.4GHz) </li><li> Bluetooth 2.1 + EDR </li><li> 辅助全球卫星定位系统 {:/}</li></td><td>{::nomarkdown} <li> GSM 机型:GSM/EDGE UMTS/HSPA </li><li> CDMA 机型:CDMA EV-DO Rev. A </li><li> WLAN (802.11b/g/n;使用 802.11n 时工作在 2.4GHz) </li><li> Bluetooth 2.1 + EDR </li><li style="color:blue"> 辅助全球卫星定位系统和 GLONASS 定位系统 {:/}</li></td><td>{::nomarkdown} <li> GSM 机型:GSM/EDGE UMTS/HSPA+ DC-HSDPA </li><li> CDMA 机型:CDMA EV-DO Rev. A 和 Rev. B </li><li style="color:red"> LTE </li><li> WLAN (802.11a/b/g/n;使用 802.11n 时可工作在 2.4GHz 和 5GHz 频段) </li><li> Bluetooth 4.0片 </li><li> 辅助全球卫星定位系统和 GLONASS 定位系统 {:/}</li></td></tr><tr><td>FaceTime</td><td>-</td><td>透過 Wi-Fi 從 iPhone 4 至任何支援 FaceTime 的裝置</td><td>透過 Wi-Fi 或流動網絡從 iPhone 4S 至任何支援 FaceTime 的裝置</td><td>透過 Wi-Fi 或流動網絡從 iPhone 5 至任何支援 FaceTime 的裝置</td></tr><tr><td>SIM</td><td>SIM 卡</td><td><em style="color:green">Micro-SIM 卡</em></td><td>Micro-SIM 卡</td><td><em style="color:red"> Nano-SIM 卡 (不兼容现有 micro-SIM 卡)</em></td></tr><tr><td>端口</td><td>30-pin</td><td>30-pin</td><td>30-pin</td><td>Lightning</td></tr><tr><td>电池寿命</td><td>通话时间: {::nomarkdown} <li> 使用 3G 网络时长达 5 小时 </li><li> 互联网使用:使用 3G 网络时长达 5 小时 </li><li> 使用 WLAN 网络时长达 9 小时 </li><li>视频播放:长达 10 小时 </li><li> 音频播放:长达 30 小时 </li><li> 待机时间:长达 300 小时 {:/}</li></td><td>通话时间: {::nomarkdown} <li> 使用 3G 网络时长达 7 小时 </li><li> 互联网使用:使用 3G 网络时长达 6 小时 </li><li> 使用 WLAN 网络时长达 10 小时 </li><li> 待机时间:长达 300 小时 {:/}</li></td><td>通话时间: {::nomarkdown} <li> 使用 3G 网络时长达 8 小时 </li><li> 互联网使用:使用 3G 网络时长达 6 小时 </li><li> 使用 WLAN 网络时长达 9 小时 </li><li> 待机时间:长达 200 小时 {:/}</li></td><td>通话时间: {::nomarkdown} <li> 使用 3G 网络时长达 8 小时 </li><li> 互联网使用:使用 3G 网络时长达 8 小时 </li><li> 使用 WLAN 网络时长达 10 小时 </li><li> 待机时间:长达 225 小时 {:/}</li></td></tr><tr><td>重量</td><td>135 克 (4.8 盎司)</td><td>137 克 (4.8 盎司)</td><td>140 克 (4.9 盎司)</td><td>112 克 (3.95 盎司)</td></tr><tr><td>三围尺寸 (高x宽x厚)</td><td>115.5 毫米 (4.5 英寸) x 62.1 毫米 (2.4 英寸) x 12.3 毫米 (0.48 英寸)</td><td>115.2 毫米 (4.5 英寸) x 58.6 毫米 (2.31 英寸) x 9.3 毫米 (0.37 英寸)</td><td>115.2 毫米 (4.5 英寸) x 58.6 毫米 (2.31 英寸) x 9.3 毫米 (0.37 英寸)</td><td>123.8 毫米 (4.87 英寸) x 58.6 毫米 (2.31 英寸) x <em style="color:red">7.6 毫米 (0.30 英寸)</em></td></tr><tr><td>颜色</td><td>黑色</td><td>黑色/白色</td><td>黑色/白色</td><td>黑配碳黑色/白配银白色</td></tr></tbody></table><table><thead><tr><th></th><th>iphone 5c</th><th>iphone 5s</th></tr></thead><tbody><tr><td>上市时间</td><td>2013.9.20</td><td>2013.9.20</td></tr><tr><td>处理器</td><td>1.3 GHz 双核 ARMv7s Apple A6</td><td><em style="color:green">1.3 GHz 双核 ARMv8-A 64-bit Apple A7 with M7 运动协处理器</em></td></tr><tr><td>iOS</td><td>ios7</td><td>ios7</td></tr><tr><td>RAM</td><td>1G LPDDR2</td><td><em style="color:blue">1G LPDDR3</em></td></tr><tr><td>屏幕</td><td>{::nomarkdown} <li> 4 英寸(对角线) Retina 显示屏 </li><li> 1136 x 640 分辨率 326 ppi {:/}</li></td><td>{::nomarkdown} <li> 4 英寸(对角线) Retina 显示屏 </li><li> 1136 x 640 分辨率 326 ppi {:/}</li></td></tr><tr><td>主相机</td><td>{::nomarkdown} <li> 800 万像素 </li><li> 轻点对焦 </li><li> 自动对焦 </li><li> LED 闪光灯 </li><li> 背部照度传感器 </li><li> 五鏡片鏡頭 </li><li> f/2.4光圈 </li><li> 面部检测 </li><li> 混合红外线滤镜 </li><li> 全景模式 {:/}</li></td><td>{::nomarkdown} <li style="color:red"> 800 万像素,单个像素尺寸为 1.5 微米 </li><li> 轻点对焦 </li><li> 自动对焦 </li><li style="color:red"> ƒ/2.2 光圈 </li><li style="color:red"> 蓝宝石水晶镜头表面 </li><li style="color:red"> True Tone 闪光灯 </li><li> 背部照度传感器 </li><li> 面部检测 </li><li style="color:red"> 五鏡片鏡頭 </li><li> 混合红外线滤镜 </li><li> 全景模式 </li><li> 自动图像防抖动功能 </li><li> 连拍模式 </li><li> 照片地理标记功能 {:/}</li></td></tr><tr><td>前置相机</td><td>{::nomarkdown} <li> 120 万像素画质照片 </li><li> 720p HD 高清视频 </li><li> 背部照度传感器 {:/}</li></td><td>{::nomarkdown} <li> 120 万像素画质照片 </li><li> 720p HD 高清视频 </li><li> 背部照度传感器 {:/}</li></td></tr><tr><td>录像</td><td>{::nomarkdown} <li> 1080p HD 高清摄像 </li><li> 每秒 30 帧 </li><li> 摄制过程中轻点对焦 </li><li> 视频防抖动 </li><li> 摄像过程中拍摄静态照片 </li><li> 面部检测 {:/}</li></td><td>{::nomarkdown} <li> 1080p HD 高清摄像 </li><li> 每秒 30 帧 </li><li> 摄制过程中轻点对焦 </li><li> 提升的视频防抖动功能 </li><li> 摄像过程中拍摄静态照片 </li><li> 面部检测 </li><li> 慢动作视频 </li><li> 拍摄视频时支持拍摄静态照片 </li><li> 3 倍变焦 {:/}</li></td></tr><tr><td>网络</td><td>{::nomarkdown} <li> Model A1532 (GSM)<em>: UMTS/HSPA+/DC-HSDPA (850, 900, 1700/2100, 1900, 2100 MHz); GSM/EDGE (850, 900, 1800, 1900 MHz); LTE (Bands 1, 2, 3, 4, 5, 8, 13, 17, 19, 20, 25) <li> Model A1532 (CDMA)</li></em>: CDMA EV-DO Rev. A and Rev. B (800, 1700/2100, 1900, 2100 MHz); UMTS/HSPA+/DC-HSDPA (850, 900, 1700/2100, 1900, 2100 MHz); GSM/EDGE (850, 900, 1800, 1900 MHz); LTE (Bands 1, 2, 3, 4, 5, 8, 13, 17, 19, 20, 25) </li><li> Model A1456<em>: CDMA EV-DO Rev. A and Rev. B (800, 1700/2100, 1900, 2100 MHz); UMTS/HSPA+/DC-HSDPA (850, 900, 1700/2100, 1900, 2100 MHz); GSM/EDGE (850, 900, 1800, 1900 MHz); LTE (Bands 1, 2, 3, 4, 5, 8, 13, 17, 18, 19, 20, 25, 26) <li>Model A1507</li></em>: UMTS/HSPA+/DC-HSDPA (850, 900, 1900, 2100 MHz); GSM/EDGE (850, 900, 1800, 1900 MHz); LTE (Bands 1, 2, 3, 5, 7, 8, 20) li Model A1529<em>: UMTS/HSPA+/DC-HSDPA (850, 900, 1900, 2100 MHz); GSM/EDGE (850, 900, 1800, 1900 MHz); FDD-LTE (Bands 1, 2, 3, 5, 7, 8, 20); TD-LTE (Bands 38, 39, 40) <li> Model A1529</li></em>: UMTS/HSPA+/DC-HSDPA (850, 900, 1900, 2100 MHz); GSM/EDGE (850, 900, 1800, 1900 MHz); FDD-LTE (Bands 1, 2, 3, 5, 7, 8, 20); TD-LTE (Bands 38, 39, 40) </li><li> 802.11a/b/g/n Wi-Fi (802.11n 2.4GHz and 5GHz) </li><li> 蓝牙4.0 {:/}</li></td><td>{::nomarkdown} <li> Model A1532 (GSM)<em>: UMTS/HSPA+/DC-HSDPA (850, 900, 1700/2100, 1900, 2100 MHz); GSM/EDGE (850, 900, 1800, 1900 MHz); LTE (Bands 1, 2, 3, 4, 5, 8, 13, 17, 19, 20, 25) <li> Model A1532 (CDMA)</li></em>: CDMA EV-DO Rev. A and Rev. B (800, 1700/2100, 1900, 2100 MHz); UMTS/HSPA+/DC-HSDPA (850, 900, 1700/2100, 1900, 2100 MHz); GSM/EDGE (850, 900, 1800, 1900 MHz); LTE (Bands 1, 2, 3, 4, 5, 8, 13, 17, 19, 20, 25) </li><li> Model A1456<em>: CDMA EV-DO Rev. A and Rev. B (800, 1700/2100, 1900, 2100 MHz); UMTS/HSPA+/DC-HSDPA (850, 900, 1700/2100, 1900, 2100 MHz); GSM/EDGE (850, 900, 1800, 1900 MHz); LTE (Bands 1, 2, 3, 4, 5, 8, 13, 17, 18, 19, 20, 25, 26) <li>Model A1507</li></em>: UMTS/HSPA+/DC-HSDPA (850, 900, 1900, 2100 MHz); GSM/EDGE (850, 900, 1800, 1900 MHz); LTE (Bands 1, 2, 3, 5, 7, 8, 20) li Model A1529<em>: UMTS/HSPA+/DC-HSDPA (850, 900, 1900, 2100 MHz); GSM/EDGE (850, 900, 1800, 1900 MHz); FDD-LTE (Bands 1, 2, 3, 5, 7, 8, 20); TD-LTE (Bands 38, 39, 40) <li> Model A1529</li></em>: UMTS/HSPA+/DC-HSDPA (850, 900, 1900, 2100 MHz); GSM/EDGE (850, 900, 1800, 1900 MHz); FDD-LTE (Bands 1, 2, 3, 5, 7, 8, 20); TD-LTE (Bands 38, 39, 40) </li><li> 802.11a/b/g/n Wi-Fi (802.11n 2.4GHz and 5GHz) </li><li> 蓝牙4.0 {:/}</li></td></tr><tr><td>FaceTime</td><td>{::nomarkdown} <li>可通过无线网络或蜂窝网络与任何支持 FaceTime 的设备进行视频通话 </li><li>可通过 DC-HSDPA、HSPA+、3G 和 2G 网络发起视频通话 </li><li>可通过无线网络连接进行 HVGA 分辨率 (480 x 368) 视频通话 {:/}</li></td><td>{::nomarkdown} <li>可通过无线网络或蜂窝网络与任何支持 FaceTime 的设备进行视频通话 </li><li>可通过 DC-HSDPA、HSPA+、3G 和 2G 网络发起视频通话 </li><li>可通过无线网络连接进行 HVGA 分辨率 (480 x 368) 视频通话 {:/}</li></td></tr><tr><td>SIM</td><td>Nano-SIM 卡 (不兼容现有 micro-SIM 卡)</td><td>Nano-SIM 卡 (不兼容现有 micro-SIM 卡)</td></tr><tr><td>端口</td><td>Lightning</td><td>Lightning</td></tr><tr><td>电池寿命</td><td>{::nomarkdown} 通话时间: <li style="color:red"> 使用 3G 网络时长达 10 小时 </li><li> 互联网使用:使用 3G 网络时长达 8 小时,使用无线网络时长达 10 小时 </li><li style="color:red"> 待机时间:长达 250 小时 </li><li>视频播放:长达 10 小时 </li><li>音频播放:长达 40 小时 {:/}</li></td><td>{::nomarkdown} 通话时间:<li> 使用 3G 网络时长达 10 小时 </li><li> 互联网使用:使用 3G 网络时长达 8 小时,使用无线网络时长达 10 小时 </li><li> 待机时间:长达 250 小时 </li><li>视频播放:长达 10 小时 </li><li>音频播放:长达 40 小时 {:/}</li></td></tr><tr><td>重量</td><td><em style="color:red">132 克 (4.658 盎司)</em></td><td>112 克 (3.95 盎司)</td></tr><tr><td>三围尺寸 (高x宽x厚)</td><td><em style="color:red">124.4 毫米 (4.90 英寸) x 59.2 毫米 (2.33 英寸) x 8.97 毫米 (0.35 英寸)</em></td><td>123.8 毫米 (4.87 英寸) x 58.6 毫米 (2.31 英寸) x 7.6 毫米 (0.30 英寸)</td></tr><tr><td>颜色</td><td>白/粉/黄/蓝/绿</td><td>深空灰/金/银</td></tr></tbody></table><h2 id="ipad"><a href="#ipad" class="headerlink" title="ipad"></a>ipad</h2><p>详见:<a href="http://en.wikipedia.org/wiki/Ipad" target="_blank" rel="noopener">http://en.wikipedia.org/wiki/Ipad</a></p><table><thead><tr><th></th><th>ipad</th><th>ipad2</th><th>new ipad</th><th>ipad 4</th></tr></thead><tbody><tr><td>出产年份</td><td>2010年1月27日</td><td>2011年3月12日</td><td>2012年3月16日</td><td>2012年10月24日</td></tr><tr><td>处理器</td><td><em style="color:red">Apple A4 1GHz</em></td><td><em style="color:green">雙核心 A5(1GHz)</em></td><td><em style="color:blue">Apple A5X (双核CPU+四核GPU,CPU主频为1GHz</em></td><td><em style="color:red">雙核心 A6X(双核CPU+四核GPU,CPU主频1.4GHz)</em></td></tr><tr><td>iOS</td><td>iOS 4</td><td>iOS 4.3</td><td>iOS 5</td><td>iOS 6</td></tr><tr><td>RAM</td><td>256MB</td><td><em style="color:green">512MB</em></td><td><em style="color:blue">1G</em></td><td>1G</td></tr><tr><td>屏幕</td><td>{::nomarkdown} <li> 1024 x 768 px </li><li> 9.7 in (25 cm) 132 ppi {:/}</li></td><td>{::nomarkdown} <li> 配備 IPS 技術的 9.7 吋 (對角) LED 背光 Multi-Touch 顯示器 </li><li> 1024 x 768 解像度,每吋 132 像素 </li><li> 防指紋的抗脂塗層 {:/}</li></td><td>{::nomarkdown} <li style="color:blue"> 高解像度 Retina 顯示 </li><li style="color:blue"> 2048 x 1536 像素解像度,每吋 264 像素 </li><li> 防指紋的抗脂塗層 </li><li> 支援多種語言文字同時顯示 {:/}</li></td><td>{::nomarkdown} <li> 高解像度 Retina 顯示 </li><li> 2048 x 1536 像素解像度,每吋 264 像素 </li><li> 防指紋的抗脂塗層 </li><li> 支援多種語言文字同時顯示 {:/}</li></td></tr><tr><td>主相机</td><td>-</td><td>{::nomarkdown} <li> 後置鏡頭 </li><li style="color:green"> 960 x 720 照片(70万像素) </li><li> 自動對焦 {:/}</li></td><td>{::nomarkdown} <li style="color:blue"> iSight 鏡頭 </li><li style="color:blue"> 500 万像素自动对焦 </li><li> 轻点自动对焦 </li><li> 静态图像面部检测 {:/}</li></td><td>{::nomarkdown} <li> iSight 鏡頭 </li><li> 5 百萬像素照片 </li><li> 自動對焦 </li><li> 面孔識別功能 </li><li> 背置式光線感應 </li><li> 五鏡片鏡頭 </li><li> 混合型 IR 濾鏡 </li><li style="color:red"> ƒ/2.4 光圈 {:/}</li></td></tr><tr><td>前置相机</td><td>-</td><td>{::nomarkdown} <li> FaceTime 鏡頭(30w) </li><li> VGA 解像度照片 </li><li> VGA 解像度影片 </li><li> 透過 Wi-Fi 進行 FaceTime 視像通話 {:/}</li></td><td>{::nomarkdown} <li> FaceTime 鏡頭(30w) </li><li> 可拍摄 VGA 画质的照片 </li><li> 每秒 30 帧的视频 {:/}</li></td><td>{::nomarkdown} <li> FaceTime HD 鏡頭 </li><li style="color:red"> 120 百萬像素照片 </li><li> 720p HD 影片 </li><li style="color:red"> 透過 Wi-Fi 或流動網絡進行 FaceTime 視像通話 </li><li> 面孔識別功能 </li><li> 背置式光線感應 {:/}</li></td></tr><tr><td>录像</td><td>-</td><td>{::nomarkdown} <li> 720p HD 影片攝錄 </li><li> 攝錄時可使用點按對焦功能 {:/}</li></td><td>{::nomarkdown} <li> 高达每秒 30 帧的 HD 高清视频 (1080p) </li><li> 带有音频视频防抖动 {:/}</li></td><td>{::nomarkdown} <li> 1080p HD 影片攝錄 </li><li> 攝錄時可使用點按對焦功能 </li><li> 影片防震功能 </li><li>面孔識別功能 </li><li> 面孔識別功能 </li><li> 背置式光線感應 {:/}</li></td></tr><tr><td>网络</td><td>{::nomarkdown} <li> 802.11a/b/g/n </li><li> 3频 hspa + edge (2g) </li><li> Bluetooth 2.1 + EDR 技術 {:/}</li></td><td>Wi-Fi {::nomarkdown} <li> Wi-Fi (802.11a/b/g/n;802.11n 適用於 2.4GHz 和 5GHz) </li><li> Bluetooth 2.1 + EDR 技術 <br> Wi-Fi + 3G </li><li> Wi-Fi (802.11a/b/g/n;802.11n 適用於 2.4GHz 和 5GHz) </li><li> Bluetooth 2.1 + EDR 技術 </li><li> GSM/EDGE </li><li> UMTS/HSPA {:/}</li></td><td>Wi-Fi {::nomarkdown} <li> Wi-Fi (802.11a/b/g/n;802.11n 適用於 2.4GHz 和 5GHz) </li><li style="color:blue"> Bluetooth 4.0 技術 <br> Wi-Fi + 3G </li><li> Wi-Fi (802.11a/b/g/n;802.11n 適用於 2.4GHz 和 5GHz) </li><li style="color:blue"> Bluetooth 4.0 技術 </li><li> GSM/EDGE </li><li> UMTS/HSPA/HSPA+/DC-HSDPA </li><li style="color:blue"> LTE4 {:/}</li></td><td>Wi-Fi {::nomarkdown} <li> Wi-Fi (802.11a/b/g/n;802.11n 適用於 2.4GHz 和 5GHz) </li><li> Bluetooth 4.0 技術 <br> Wi-Fi + Cellular </li><li> Wi-Fi (802.11a/b/g/n;802.11n 適用於 2.4GHz 和 5GHz) </li><li> Bluetooth 4.0 技術 </li><li> GSM/EDGE </li><li> UMTS/HSPA/HSPA+/DC-HSDPA </li><li> LTE4 {:/}</li></td></tr><tr><td>SIM</td><td>micro-SIM</td><td>Micro-SIM</td><td>Micro-SIM</td><td>Micro-SIM</td></tr><tr><td>端口</td><td>30 針</td><td>30 針</td><td>30 針</td><td><em style="color:red"> Lightning</em></td></tr><tr><td>电池寿命</td><td>{::nomarkdown} <li> 以 Wi-Fi 瀏覽網頁、觀看影片或聆聽音樂長達 10 小時 </li><li> 透過流動數據網絡上網長達 9 小時 {:/}</li></td><td>{::nomarkdown} <li> 以 Wi-Fi 瀏覽網頁、觀看影片或聆聽音樂長達 10 小時 </li><li> 透過流動數據網絡上網長達 9 小時 {:/}</li></td><td>{::nomarkdown} <li> 以 Wi-Fi 瀏覽網頁、觀看影片或聆聽音樂長達 10 小時 </li><li> 透過流動數據網絡上網長達 9 小時 {:/}</li></td><td>{::nomarkdown} <li> 以 Wi-Fi 瀏覽網頁、觀看影片或聆聽音樂長達 10 小時 </li><li> 透過流動數據網絡上網長達 9 小時 {:/}</li></td></tr><tr><td>重量</td><td>{::nomarkdown} <li> 680g (wifi) </li><li> 730g (wifi + 3g){:/}</li></td><td>{::nomarkdown} <li> 601 克 (1.33 磅) (wifi) </li><li> 613 克 (1.35 磅) (wifi + 3g) {:/}</li></td><td>{::nomarkdown} <li> 652 克(1.44 磅) (wifi) </li><li> 662 克(1.46 磅) (wifi + 3g) {:/}</li></td><td>{::nomarkdown} <li> 652 克(1.44 磅) (wifi) </li><li> 662 克(1.46 磅) (wifi + Cellular) {:/}</li></td></tr><tr><td>三围尺寸(高x宽x厚)</td><td>243×190×13.4 mm(9.56×7.47×0.528 英寸)</td><td>241.2×185.7×8.8 mm(9.50×7.31×0.34 英寸)</td><td>241.2×185.7×9.4 mm(9.50×7.31×0.37 英寸)</td><td>241.2×185.7×9.4 mm(9.50×7.31×0.37 英寸)</td></tr><tr><td>颜色</td><td>-</td><td>黑、白</td><td>黑、白</td><td>黑、白</td></tr><tr><td>内置助理</td><td>-</td><td>-</td><td>siri</td><td>siri</td></tr></tbody></table><table><thead><tr><th></th><th>ipad air</th></tr></thead><tbody><tr><td>出产年份</td><td>2013年11月1日</td></tr><tr><td>处理器</td><td><em style="color:green">1.4 GHz 双核 A7)</em></td></tr><tr><td>iOS</td><td>iOS 7</td></tr><tr><td>RAM</td><td>1G LPDDR3</td></tr><tr><td>屏幕</td><td>{::nomarkdown} <li> Retina 显示屏 </li><li> 9.7 英寸 (对角线) LED 背光 Multi-Touch 显示屏,具有 IPS 技术 </li><li> 2048 x 1536 像素分辨率,264 ppi </li><li> 采用防油渍防指纹涂层 {:/}</li></td></tr><tr><td>主相机</td><td>{::nomarkdown} <li> iSight 鏡頭 </li><li> 5 百萬像素照片 </li><li> 自動對焦 </li><li> 面孔識別功能 </li><li> 背置式光線感應 </li><li> 五鏡片鏡頭 </li><li> 混合型 IR 濾鏡 </li><li> ƒ/2.4 光圈 </li><li>轻点控制视频或照片曝光 </li><li>照片和视频地理标记功能 </li><li>HDR 照片 {:/}</li></td></tr><tr><td>前置相机</td><td>{::nomarkdown} <li> FaceTime HD 鏡頭 </li><li> 120 百萬像素照片 </li><li> 720p HD 影片 </li><li> 透過 Wi-Fi 或流動網絡進行 FaceTime 視像通話 </li><li> 面孔識別功能 </li><li> 背置式光線感應 </li><li>轻点控制视频或照片曝光 </li><li>照片和视频地理标记功能 {:/}</li></td></tr><tr><td>录像</td><td>{::nomarkdown} <li> 1080p HD 影片攝錄 </li><li> 视频防抖动 </li><li> 面部检测 </li><li>摄制过程中轻点对焦 </li><li> 3 倍视频变焦 </li><li> 背置式光線感應 {:/}</li></td></tr><tr><td>网络</td><td>Wi-Fi {::nomarkdown} <li> Wi-Fi (802.11a/b/g/n;802.11n 適用於 2.4GHz 和 5GHz) </li><li>MIMO 技术 </li><li> Bluetooth 4.0 技術 <br> Wi-Fi + Cellular </li><li> Wi-Fi (802.11a/b/g/n;802.11n 適用於 2.4GHz 和 5GHz) </li><li>MIMO 技术 </li><li> Bluetooth 4.0 技術 </li><li> UMTS/HSPA/HSPA+/DC-HSDPA (850, 900, 1700/2100, 1900, 2100 MHz); GSM/EDGE (850, 900, 1800, 1900 MHz) </li><li> CDMA EV-DO Rev. A and Rev. B (800, 1900 MHz) </li><li> LTE (Bands 1, 2, 3, 4, 5, 7, 8, 13, 17, 18, 19, 20, 25, 26) {:/}</li></td></tr><tr><td>SIM</td><td><em style="color:red"> Nano-SIM</em></td></tr><tr><td>端口</td><td>Lightning</td></tr><tr><td>电池寿命</td><td>{::nomarkdown} <li> 以 Wi-Fi 瀏覽網頁、觀看影片或聆聽音樂長達 10 小時 </li><li> 透過流動數據網絡上網長達 9 小時 {:/}</li></td></tr><tr><td>重量</td><td>{::nomarkdown} <li style="color:red"> 469 克(1 磅) (wifi) </li><li style="color:red"> 478 克(1.05 磅) (wifi + Cellular) {:/}</li></td><td><em style="color:red"> 240×169.5×7.5 mm(9.4×6.6×0.29 英寸)</em></td></tr><tr><td>颜色</td><td>深空灰、白</td></tr><tr><td>内置助理</td><td>siri</td></tr></tbody></table><table><thead><tr><th></th><th>ipad mini</th><th>ipad mini with retina display</th></tr></thead><tbody><tr><td>出产年份</td><td>2012年10月24日</td><td>2013.11.12</td></tr><tr><td>处理器</td><td>雙核心 A5(1GHz)</td><td>1.3 GHz 双核 A7</td></tr><tr><td>iOS</td><td>iOS 6</td><td>iOS 7</td></tr><tr><td>RAM</td><td>512MB</td><td><em style="color:red"> 1G LPDDR3</em></td></tr><tr><td>屏幕</td><td>{::nomarkdown} <li> 配備 IPS 技術的 7.9 吋 (對角) LED 背光 Multi-Touch 顯示器 </li><li> 1024 x 768 解像度,每吋 163 像素 </li><li> 防指紋的抗脂塗層 {:/}</li></td><td>{::nomarkdown} <li> Retina 显示屏 </li><li> 7.9 英寸 (对角线) LED 背光 Multi-Touch 显示屏,具有 IPS 技术 </li><li> 2048 x 1536 像素分辨率,326 ppi </li><li> 采用防油渍防指纹涂层 {:/}</li></td></tr><tr><td>主相机</td><td>{::nomarkdown} <li> iSight 鏡頭 </li><li> 5 百萬像素照片 </li><li> 自動對焦 </li><li> 面孔識別功能 </li><li> 背置式光線感應 </li><li> 五鏡片鏡頭 </li><li> 混合型 IR 濾鏡 </li><li> ƒ/2.4 光圈 {:/}</li></td><td>{::nomarkdown} <li> iSight 鏡頭 </li><li> 5 百萬像素照片 </li><li> 自動對焦 </li><li> 面孔識別功能 </li><li> 背置式光線感應 </li><li> 五鏡片鏡頭 </li><li> 混合型 IR 濾鏡 </li><li> ƒ/2.4 光圈 </li><li>轻点控制视频或照片对焦 </li><li>轻点控制视频或照片曝光 </li><li>照片和视频地理标记功能 </li><li>HDR 照片 {:/}</li></td></tr><tr><td>录像</td><td>{::nomarkdown} <li> 1080p HD 影片攝錄 </li><li> 攝錄時可使用點按對焦功能 </li><li> 影片防震功能 </li><li>面孔識別功能 </li><li> 面孔識別功能 </li><li> 背置式光線感應 {:/}</li></td><td>{::nomarkdown} <li> 1080p HD 影片攝錄 </li><li> 视频防抖动 </li><li> 面部检测 </li><li> 摄制过程中轻点对焦 </li><li>背照式感光 </li><li> 3 倍视频变焦 {:/}</li></td></tr><tr><td>网络</td><td>Wi-Fi {::nomarkdown} <li> Wi-Fi (802.11a/b/g/n;802.11n 適用於 2.4GHz 和 5GHz) </li><li> Bluetooth 4.0 技術 <br> Wi-Fi + Cellular </li><li> Wi-Fi (802.11a/b/g/n;802.11n 適用於 2.4GHz 和 5GHz) </li><li> Bluetooth 4.0 技術 </li><li> GSM/EDGE </li><li> UMTS/HSPA/HSPA+/DC-HSDPA </li><li> LTE4 {:/}</li></td><td>Wi-Fi {::nomarkdown} <li> Wi-Fi (802.11a/b/g/n;802.11n 適用於 2.4GHz 和 5GHz) </li><li>MIMO 技术 </li><li> Bluetooth 4.0 技術 <br> Wi-Fi + Cellular </li><li> Wi-Fi (802.11a/b/g/n;802.11n 適用於 2.4GHz 和 5GHz) </li><li>MIMO 技术 </li><li> Bluetooth 4.0 技術 </li><li> UMTS/HSPA/HSPA+/DC-HSDPA (850, 900, 1700/2100, 1900, 2100 MHz); GSM/EDGE (850, 900, 1800, 1900 MHz) </li><li> CDMA EV-DO Rev. A and Rev. B (800, 1900 MHz) </li><li> LTE (Bands 1, 2, 3, 4, 5, 7, 8, 13, 17, 18, 19, 20, 25, 26) {:/}</li></td></tr><tr><td>SIM</td><td>Nano-SIM (與現有 micro-SIM 卡並不兼容)</td></tr><tr><td>端口</td><td>Lightning</td><td>Lightning</td></tr><tr><td>电池寿命</td><td>{::nomarkdown} <li> 以 Wi-Fi 瀏覽網頁、觀看影片或聆聽音樂長達 10 小時 </li><li> 透過流動數據網絡上網長達 9 小時 {:/}</li></td><td>{::nomarkdown} <li> 以 Wi-Fi 瀏覽網頁、觀看影片或聆聽音樂長達 10 小時 </li><li> 透過流動數據網絡上網長達 9 小時 {:/}</li></td></tr><tr><td>重量</td><td>{::nomarkdown} <li> 308 克(0.68 磅) (wifi) </li><li> 312 克(0.69 磅) (wifi + Cellular) {:/}</li></td><td>{::nomarkdown} <li style="color:red"> 331 克(0.73 磅) (wifi) </li><li style="color:red"> 341 克(0.75 磅) (wifi + Cellular) {:/}</li></td></tr><tr><td>三围尺寸(高x宽x厚)</td><td>200×134.7×7.2 mm(7.87×5.31×0.28 英寸)</td><td><em style="color:red">200×134.7×7.5 mm(7.87×5.31×0.29 英寸)</em></td></tr><tr><td>颜色</td><td>黑、白</td><td>深空灰、白</td></tr><tr><td>内置助理</td><td>siri</td><td>siri</td></tr></tbody></table><h2 id="itouch"><a href="#itouch" class="headerlink" title="itouch"></a>itouch</h2><p>详见:<a href="http://en.wikipedia.org/wiki/IPod_Touch" target="_blank" rel="noopener">http://en.wikipedia.org/wiki/IPod_Touch</a></p><table><thead><tr><th></th><th>itouch 1</th><th>itouch 2</th><th>itouch 3</th><th>itouch 4</th><th>itouch 5</th></tr></thead><tbody><tr><td>出产年份</td><td>5-Sep-07</td><td>9-Sep-08</td><td>9-Sep-09</td><td>1-Sep-10</td><td>15-Oct-12</td></tr><tr><td>处理器</td><td><em style="color:red">ARM 1176JZ(F)-S 620MHz</em></td><td><em style="color:green">ARM 1176JZ(F)-S 620MHz</em></td><td><em style="color:blue">ARM Cortex-A8 833MHz</em></td><td><em style="color:red">A4</em></td><td><em style="color:green"> A5</em></td></tr><tr><td>最高支持iOS</td><td>iPhone OS 3.1.3</td><td>iOS 4.2.1</td><td>iOS 5.1.1</td><td>iOS 6.0</td><td>iOS 6.0</td></tr><tr><td>RAM</td><td>128 MB</td><td>128 MB</td><td><em style="color:blue">256MB</em></td><td>256MB</td><td><em style="color:green"> 512MB</em></td></tr><tr><td>容量</td><td>8 GB/16 GB/32 GB</td><td>8 GB/16 GB/32 GB</td><td>32 GB/64 GB</td><td>8 GB/16 GB/32 GB/64 GB</td><td>16 GB/32 GB/64 GB</td></tr><tr><td>屏幕</td><td>{::nomarkdown} <li>3.5 英寸(对角线)宽屏 Multi-Touch 显示器 </li><li>480 x 320 像素分辨率,163像素/英寸 {:/}</li></td><td>{::nomarkdown} <li>3.5 英寸(对角线)宽屏 Multi-Touch 显示器 </li><li>480 x 320 像素分辨率,163像素/英寸 {:/}</li></td><td>{::nomarkdown} <li>3.5 英寸(对角线)宽屏 Multi-Touch 显示器 </li><li>480 x 320 像素分辨率,163像素/英寸 {:/}</li></td><td>{::nomarkdown} <li>Retina 显示屏,Multi-Touch 宽屏显示屏 </li><li>960 x 640 像素分辨率,326像素/英寸 {:/}</li></td><td>{::nomarkdown} <li>Retina 显示屏,4 英寸(对角线)宽屏显示屏, 具有 Multi-Touch IPS 技术 </li><li>1136 x 640 像素分辨率,326像素/英寸 {:/}</li></td></tr><tr><td>主相机</td><td>-</td><td>-</td><td>-</td><td><em style="color:red">CMOS image sensor with video (720p HD at 30 frames/s)</em></td><td><em style="color:green">500 万像素 iSight 摄像头(1080p at up to 30 fps)</em></td></tr><tr><td>前置相机</td><td>-</td><td>-</td><td>- {:/}</td><td>{::nomarkdown} <li> VGA-quality photos </li><li> video at up to 30 frames/s {:/}</li></td><td>{::nomarkdown} <li> 1.2 MP photos </li><li> 720p at up to 30 fps {:/}</li></td></tr><tr><td>连接性</td><td>Wi-Fi (802.11b/g) {:/}</td><td>{::nomarkdown} <li>Wi-Fi (802.11b/g) </li><li style="color:green">Bluetooth 2.1 + EDR (requires iPhone OS 3.0) </li><li>Built-in speaker, hardware volume controls {:/}</li></td><td>{::nomarkdown} <li>Wi-Fi (802.11b/g) </li><li>Bluetooth 2.1 + EDR (requires iPhone OS 3.0) </li><li>Built-in speaker, hardware volume controls </li><li> Voice control {:/}</li></td><td>{::nomarkdown} <li>Wi-Fi (802.11b/g) </li><li style="color:red"> 802.11n (2.4 GHz only)</li><li>Bluetooth 2.1 + EDR (requires iPhone OS 3.0) </li><li>Built-in speaker, hardware volume controls </li><li> Voice control </li><li style="color:red">3轴陀螺仪 {:/}</li></td><td>{::nomarkdown} <li>Wi-Fi (802.11b/g) </li><li style="color:green"> 802.11n (2.4 GHz and 5.0 GHz) </li><li style="color:green">Bluetooth 4.0 + EDR (requires iPhone OS 3.0) </li><li>Built-in speaker, hardware volume controls </li><li> Voice control </li><li>3轴陀螺仪 {:/}</li></td></tr><tr><td>端口</td><td>30 針</td><td>30 針</td><td>30 針</td><td>30 針</td><td><em style="color:green"> Lightning</em></td></tr><tr><td>电池寿命</td><td>{::nomarkdown} <li> 音频播放:长达 22 小时 </li><li>视频播放:长达 5 小时 {:/}</li></td><td>{::nomarkdown} <li> 音频播放:长达 36 小时 </li><li>视频播放:长达 6 小时 {:/}</li></td><td>{::nomarkdown} <li> 音频播放:长达 30 小时 </li><li>视频播放:长达 6 小时 {:/}</li></td><td>{::nomarkdown} <li> 音频播放:长达 40 小时 </li><li>视频播放:长达 7 小时 {:/}</li></td><td>{::nomarkdown} <li> 音频播放:长达 40 小时 </li><li>视频播放:长达 8 小时 {:/}</li></td></tr><tr><td>重量</td><td>120克</td><td>115 克</td><td>115 克</td><td>101 克</td><td>88 克</td></tr><tr><td>三围尺寸(高x宽x厚)</td><td>110x61.8x8 毫米</td><td>110x61.8x8.5 毫米</td><td>110x61.8x8.5 毫米</td><td>111.0×58.9×7.2 毫米</td><td>123.4×58.6×6.1 毫米</td></tr><tr><td>颜色</td><td>黑</td><td>黑</td><td>黑</td><td>黑、银</td><td><em style="color:green">黑、银、粉红、红、黄、蓝</em></td></tr></tbody></table><p>哇哈哈,终于整理完了,好辛苦。。。</p>]]></content>
<summary type="html">
apple products comparison
</summary>
<category term="Usage" scheme="https://ddrccw.github.io/categories/Usage/"/>
<category term="iPad" scheme="https://ddrccw.github.io/tags/iPad/"/>
<category term="iPhone" scheme="https://ddrccw.github.io/tags/iPhone/"/>
<category term="iTouch" scheme="https://ddrccw.github.io/tags/iTouch/"/>
</entry>
<entry>
<title>iOS开发过程中常见的debug技巧</title>
<link href="https://ddrccw.github.io/2012/09/04/2012-09-04-xcode-debug-issues/"/>
<id>https://ddrccw.github.io/2012/09/04/2012-09-04-xcode-debug-issues/</id>
<published>2012-09-04T08:21:55.000Z</published>
<updated>2018-08-11T14:02:42.663Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>记得刚学ios那会儿,我还不会用debug工具。编程时,最痛苦的莫过于程序莫名其妙的在main函数crash,其中,SIGABRT、EXC_BAD_ACCESS、Assertion failure等情况居多。虽然也看了一些资料,但是一直也没怎么系统的整理过相关知识,故特此整理一下。</p><h2 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h2><p>虽然有高手可以纯粹用<a href="http://www.mikeash.com/pyblog/friday-qa-2011-06-17-gdb-tips-and-tricks.html" title="gdb" target="_blank" rel="noopener">gdb</a>直接调试,但我等菜鸟还是利用一下Xcode的提供的图形界面,保证一下工作效率:P</p><h3 id="1-异常捕捉"><a href="#1-异常捕捉" class="headerlink" title="1 异常捕捉"></a>1 异常捕捉</h3><p>想必这也是最最基本的步骤,在Xcode导航栏找到断点导航栏,如图添加Exception breakpoint</p><p><img src="/images/xcode-debug-issue/1.1.jpeg" alt="alt exception" title="exception"></p><p>如果想有针对性的步骤异常,比如Objective-C exception,可以如图添加symbolic breakpoint</p><p><img src="/images/xcode-debug-issue/1.2.jpeg" alt="alt symbolic" title="symbolic"></p><p>有了上述的操作,大部分的crash都会定位到相应的代码位置。</p><h3 id="2-巧用寄存器参数"><a href="#2-巧用寄存器参数" class="headerlink" title="2 巧用寄存器参数"></a>2 巧用寄存器参数</h3><p>在汇编语言中,<a href="!http://en.wikipedia.org/wiki/Function_prologue">Function prologue</a>会准备好函数要用到的栈和寄存器,而寄存器中包含有传递给函数的参数信息。我们可以利用这部分信息进一步诊断异常。</p><p>到目前为止,由于iOS 模拟器是以i386(32位)模式运行在os x系统上,而iOS设备是基于arm结构的处理器,<br>参考<a href="http://developer.apple.com/library/ios/#technotes/tn2239/_index.html" target="_blank" rel="noopener">官方文档</a>和<a href="http://www.clarkcox.com/blog/2009/02/04/inspecting-obj-c-parameters-in-gdb/" target="_blank" rel="noopener">这篇文章</a>,我们可以在gdb或lldb中print一些信息</p><table><thead><tr><th></th><th>arm</th><th>i386 (before prolog)</th><th>i386 (after prolog) </th></tr></thead><tbody><tr><td>previous frame</td><td>po $lr</td><td>-</td><td>po *(id*)($ebp) </td></tr><tr><td>return address / receiver</td><td>po $r0</td><td>po *(id*)($esp)</td><td>po *(id*)($ebp + 4) </td></tr><tr><td>1st parameter / SEL</td><td>p (SEL)$r1</td><td>p *(SEL*)($esp + 4)</td><td>p *(SEL*)($ebp + 8) </td></tr><tr><td>2nd parameter</td><td>po $r2</td><td>po *(id*)($esp + 8)</td><td>po *(id*)($ebp + 12) </td></tr><tr><td>3rd parameter</td><td>po $r3</td><td>po *(id*)($esp + 12)</td><td>po *(id*)($ebp + 16) </td></tr><tr><td>… and so on</td><td>po *(id*)($sp + 4n) n>=0</td><td>po *(id*)($esp + 4n) n>=4</td><td>po *(id*)($ebp + 4n) n>=5 </td></tr></tbody></table><p>另外,函数返回的结果也会保留在寄存器中,在arm和i386中分别对应($r0)和($eax)。</p><p>上述的寄存器($r0或$eax)中,可能的情况(仅涉及我遇到过的情况)是</p><ul><li><p>一个NSException</p><p> 在gdb或lldb中,尝试一下<code>po [$reg]</code> 或访问相关属性 <code>po [$reg class]</code> 、<code>po [$reg name]</code>、 <code>po [$reg reason]</code></p></li><li><p>一些状况的描述</p><p> 以我在给项目添加GHUnit做单元测试的过程中遇到的问题为例,当我自以为已经成功引入GHUnit,尝试运行相应的Target时,Xcode直接在main.m中报错</p><p> <code>Assertion failure in int UIApplicationMain(int, char **, NSString *, NSString *)(), /SourceCache/UIKit_Sim/UIKit-1912.3/UIApplication.m:1601</code></p><p> 咋看之下,确实没啥头绪,只知道这里有问题,当我在终端输入<br><code>po $eax</code>后,便显示出详细的原因:</p><p> <code>(unsigned int) $2 = 109471968 Unable to instantiate the UIApplication delegate instance. No class named GHUnitIOSAppDelegate is loaded.</code></p><p> 通过这个提示,我才想起来忘记添加编译链接需要的-ObjC -all_load</p></li></ul><h3 id="3-系统环境变量使用"><a href="#3-系统环境变量使用" class="headerlink" title="3 系统环境变量使用"></a>3 系统环境变量使用</h3><h4 id="1)-BSD提供的debug辅助功能"><a href="#1)-BSD提供的debug辅助功能" class="headerlink" title="1) BSD提供的debug辅助功能"></a>1) BSD提供的debug辅助功能</h4><p>比较常用的是MallocStackLogging</p><blockquote></blockquote><p>Record backtraces for each memory block to assist memory debugging tools; if the block is allocated and then immediately freed, both entries are removed from the log, which helps reduce the size of the log</p><p>它主要提供的是memory allocator的相关信息,你也可以<code>man malloc</code>查看其他环境变量的应用。</p><h4 id="2)-obj-c-Foundation提供的debug辅助功能"><a href="#2)-obj-c-Foundation提供的debug辅助功能" class="headerlink" title="2) obj-c Foundation提供的debug辅助功能"></a>2) obj-c Foundation提供的debug辅助功能</h4><p>比较常用的是NSZombieEnabled,设为YES,则可以很容易debug给一个已经释放的对象发送消息有关的问题。<br>原理嘛,就是让deallocated的对象并没有真正的被deallocated,从而让系统能够检测到zombie对象,而执行断点指令。</p><p>适用条件:</p><blockquote><p>NSZombieEnabled also affects Core Foundation objects as well as Objective-C objects. However, you’ll only be notified when you access a zombie Core Foundation object from Objective-C, not when you access it via a CF API.</p></blockquote><h4 id="3)实践应用"><a href="#3)实践应用" class="headerlink" title="3)实践应用"></a>3)实践应用</h4><p>Xcode中,如图1,在xcode菜单栏->product->edit scheme,可以添加相应的环境变量</p><p><img src="/images/xcode-debug-issue/3.1.jpeg" alt="alt env" title="env"></p><p>或如图2,也可以在Diagnostics中直接勾选相应的选项</p><p><img src="/images/xcode-debug-issue/3.2.jpeg" alt="alt Diagnostics" title="Diagnostics"></p><p>实际开发过程中,EXC_BAD_ACCESS,objc_msgSend相关的crash往往和retain、release不当有关,可以利用上述的两个环境变量检测问题。例如下述代码</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="built_in">NSObject</span> *o = [<span class="built_in">NSObject</span> new]; </span><br><span class="line"><span class="built_in">NSMutableArray</span> *test = [[<span class="built_in">NSMutableArray</span> alloc] initWithCapacity:<span class="number">1</span>];</span><br><span class="line">[test release];</span><br><span class="line">[test addObject:o];</span><br><span class="line">[o release];</span><br></pre></td></tr></table></figure><p>开启NSZombieEnabled,可以获得信息:</p><p><code>2012-09-04 17:21:55.925 test[1705:11303] *** -[__NSArrayM addObject:]: message sent to deallocated instance 0x741fc40</code></p><p>开启MallocStackLogging,在gdb(不是lldb)终端输入</p><p><code>shell malloc_history 1786 0x741fc40 --1786 "app_pid"; 0x741fc40 "deallocated instance"</code></p><p>就可以获得一份有关malloc的详细信息。</p><h3 id="4-进阶操作"><a href="#4-进阶操作" class="headerlink" title="4 进阶操作"></a>4 进阶操作</h3><p>看了前面提到某高手<a href="http://www.mikeash.com/pyblog/friday-qa-2011-06-17-gdb-tips-and-tricks.html" title="gdb" target="_blank" rel="noopener">gdb</a>调试过程,你应该也了解了gdb的强大,想熟练使用gdb的相关命令,请参看:</p><p><a href="http://sourceware.org/gdb/download/onlinedocs/gdb/index.html" target="_blank" rel="noopener">http://sourceware.org/gdb/download/onlinedocs/gdb/index.html</a></p><p>另外,Xcode默认使用lldb也很强大,这里也提供一个gdb和lldb的命令对照表</p><p><a href="http://lldb.llvm.org/lldb-gdb.html" target="_blank" rel="noopener">http://lldb.llvm.org/lldb-gdb.html</a></p><p>最后,apple提供的测试工具也给我们能够可视化调试提供了不少便利</p><p><a href="http://developer.apple.com/library/ios/#DOCUMENTATION/DeveloperTools/Conceptual/InstrumentsUserGuide/Introduction/Introduction.html" target="_blank" rel="noopener">http://developer.apple.com/library/ios/#DOCUMENTATION/DeveloperTools/Conceptual/InstrumentsUserGuide/Introduction/Introduction.html</a></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>要真真正正的做好debug的方方面面也不是一件容易的活儿。虽然可能%20的手段,足以应付你的编程任务(至少对我来说是这样的),但是剩下的%80了解一下也无妨,肯定是有所裨益的 ^_^</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><p>1) <a href="http://developer.apple.com/library/ios/#technotes/tn2239/_index.html" target="_blank" rel="noopener">iOS Debugging Magic</a></p><p>2) <a href="http://cocoadev.com/wiki/NSZombie" target="_blank" rel="noopener">http://cocoadev.com/wiki/NSZombie</a></p><p>3) <a href="http://sealiesoftware.com/blog/archive/2008/09/22/objc_explain_So_you_crashed_in_objc_msgSend.html" target="_blank" rel="noopener">objc_explain_So_you_crashed_in_objc_msgSend</a></p><p>4) <a href="http://www.clarkcox.com/blog/2009/02/04/inspecting-obj-c-parameters-in-gdb/" target="_blank" rel="noopener">http://www.clarkcox.com/blog/2009/02/04/inspecting-obj-c-parameters-in-gdb/</a></p><p>5) <a href="http://www.raywenderlich.com/10209/my-app-crashed-now-what-part-1" target="_blank" rel="noopener">http://www.raywenderlich.com/10209/my-app-crashed-now-what-part-1</a></p>]]></content>
<summary type="html">
Xcode debug issues
</summary>
<category term="iOS-dev" scheme="https://ddrccw.github.io/categories/iOS-dev/"/>
<category term="iOS" scheme="https://ddrccw.github.io/tags/iOS/"/>
<category term="debug" scheme="https://ddrccw.github.io/tags/debug/"/>
<category term="gdb" scheme="https://ddrccw.github.io/tags/gdb/"/>
<category term="lldb" scheme="https://ddrccw.github.io/tags/lldb/"/>
<category term="Xcode" scheme="https://ddrccw.github.io/tags/Xcode/"/>
</entry>
<entry>
<title>在github上用jekyll搭建个人blog</title>
<link href="https://ddrccw.github.io/2012/08/13/2012-08-13-blogging-with-jekyll/"/>
<id>https://ddrccw.github.io/2012/08/13/2012-08-13-blogging-with-jekyll/</id>
<published>2012-08-13T13:37:13.000Z</published>
<updated>2018-08-11T09:43:45.737Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>说起写个人blog,如果是在学生时代,我肯定会觉得不可思议,理由很简单–我很懒的动笔。但让我燃起写blog的决心,却还得从工作后开始说起。平常工作中,我难免会遇到各种各样的问题。有了问题,自然需要放狗搜索,一篇一篇的翻看过去。如果是过去,我如果看到好文会保存文章链接到书签,有时往往一个问题的解决会让我收集好多个书签,于是随着时间的延长,我的浏览器书签栏也越来越臃肿。更可恶的是,有时一个同样问题的解决,由于我个人理解的不够深入或记忆不足等原因,最终导致我又得打开一个个书签去看。这样与其浪费找同样问题的时间,还不如花点时间记点笔记整理一下思路来加深理解。由此权衡利弊,我不得不开始blog,理由同样很简单–想偷懒。</p><p>之前的一段时间,我曾用wordpress+nginx+mysql分别在win和linux下搭过一个blog。但自从我开始在mac下从事ios开发并接触了git期间,我偶然了解到大名鼎鼎的github可以针对个人或组织用jekll免费搭建站点。于是,我怀着好奇心,做了一番研究和尝试之后,最终我果断选择了jekyll。它吸引我的一个重要原因就是,让我能花更多的时间用来写文字,而只需要一些终端命令就能通过简洁的markdown语法实现很好的排版并合成页面。</p><h2 id="正题"><a href="#正题" class="headerlink" title="正题"></a>正题</h2><p>前面废话了一堆,现在开始动手搭blog。以下的步骤可能不太详细,但相信大体的流程还是清晰的。</p><h3 id="1-申请一个github账户"><a href="#1-申请一个github账户" class="headerlink" title="1 申请一个github账户"></a>1 申请一个github账户</h3><p>这个应该不用解释了,有了账户就能建立两种page</p><ol><li>user/organization page <code>--用于个人和组织的主页</code></li><li>project page <code>--用于自己账户下对应项目的主页</code></li></ol><p>因为是个人blog,所以我选择1.<br>另外为了方便管理仓库,我使用ssh的方式来建立自己的电脑和github的连接,这样也省的每次更新的时候要输账户和密码。具体参考<a href="https://help.github.com/articles/generating-ssh-keys" target="_blank" rel="noopener">https://help.github.com/articles/generating-ssh-keys</a></p><h3 id="2-基础篇"><a href="#2-基础篇" class="headerlink" title="2 基础篇"></a>2 基础篇</h3><h4 id="2-1-rubyGem-for-installing-jekyll"><a href="#2-1-rubyGem-for-installing-jekyll" class="headerlink" title="2.1 rubyGem for installing jekyll"></a>2.1 rubyGem for installing jekyll</h4><p>谷歌了解了一下,Jekyll是一个简单的使用Ruby的文章内容生成器。看来要使用jekll之前还得先搞定ruby= =|||</p><p>由于我没接触过ruby,所以我在这里花点篇幅来介绍下如何安装ruby。</p><p>1) mac环境:</p><p>mac下ruby版本太低,要先升级。需要安装rvm。但是还要先解决curl证书问题。</p><p>a 获取curl证书 </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl http://curl.haxx.se/ca/cacert.pem>cacert.pem</span><br></pre></td></tr></table></figure><p>b 设置相关的环境变量</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> CURL_CA_BUNDLE=~/.ssh/cacert.pem</span><br></pre></td></tr></table></figure><p>c 安装rvm并设置环境变量</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -L get.rvm.io | bash -s stable [[ -s <span class="string">"/Users/<username>/.rvm/scripts/rvm"</span> ]] && <span class="built_in">source</span> <span class="string">"/Users/<username>/.rvm/scripts/rvm"</span> <span class="comment">#注意<username>替换成你自己的</span></span><br></pre></td></tr></table></figure><p>d 安装ruby</p><blockquote></blockquote><ol><li>Open Terminal</li><li>sudo rvm install 1.9.3</li><li>Now that Ruby is installed you can type the following command to use the newer version of Ruby: rvm use 1.9.3</li><li>To verify type: ruby -v</li><li>To make 1.9.3 the default: rvm –default 1.9.3</li></ol><p>e 安装jekyll</p><p>有了ruby,安装jekyll水到渠成。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo gem install jekyll</span><br></pre></td></tr></table></figure><h4 id="2-2-jekyll推荐工具"><a href="#2-2-jekyll推荐工具" class="headerlink" title="2.2 jekyll推荐工具"></a>2.2 jekyll推荐工具</h4><p><strong>2.2.1 安装rdiscount来解析markdown语言</strong></p><p>虽然jekyll默认用maruku来解析markdown语言,但都说用c语言实现的rdiscount解析器的速度更快,所以我也直接选择rdiscount。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo gem install rdiscount</span><br></pre></td></tr></table></figure><p>//2018.1.6</p><p>官方参考配置使用kramdown</p><p><strong>2.2.2 pygments–支持语法高亮,要装python</strong></p><p>身为挨踢攻城狮的blog,难免要贴一些代码。jekyll虽然原生不支持高亮代码,但支持通过插件工具来高亮代码。<a href="http://pygments.org" title="pygments" target="_blank" rel="noopener">pygments</a>就是一个比较流行的插件工具,它支持的语言种类相当丰富。</p><p>mac上它可以通过Macports和Homebrew,我习惯使用前者。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo port install python25 py25-pygments</span><br><span class="line">sudo ln -s <py25-pygments> <pygments> <span class="comment">#非必须:因为jekyll只能识别pygments这个名称,但是port下来的可执行文件的名称可能并不是pygments,所以我在这里创建符号链接</span></span><br></pre></td></tr></table></figure><p>//2015.6.25</p><p><del>jekyll升级到2.5.3后,pygments自带了,可以省略这步。</del> </p><p>//2018.1.6</p><p>官方参考配置使用rouge</p><h4 id="2-3-Last-But-Not-The-Least"><a href="#2-3-Last-But-Not-The-Least" class="headerlink" title="2.3 Last But Not The Least"></a>2.3 Last But Not The Least</h4><p>有了前面的基础,可以说离成功建站只有一步之遥。有技术的web开发人士大可直接自己开发,但是我等菜鸟还是可以先clone借鉴一下别人的站点风格+.+。。。</p><p>下面以我自己的为例,纯属参考。</p><p>首先,可以在<code>https://github.com/mojombo/jekyll/wiki/Sites</code>找找看有没有自己喜欢的站点。</p><p>我的站点风格来自<code>http://bilalh.github.com</code></p><p><strong>2.3.1 代码借鉴</strong></p><ol><li><code>git clone https://github.com/Bilalh/bilalh.github.com.git</code></li><li><p>安装必要的插件工具</p><p> <del><code>sudo gem install nokogiri sass growl jekyll-pagination</code></del></p><p> <del><code>sudo gem install nokogiri sass growl</code></del></p><p> <del><code>sudo gem install nokogiri growl</code></del></p><p> <code>sudo gem install nokogiri growl jekyll-paginate //2018.1.6</code></p><p> //2013.9.15</p><p> <del>ps: 因为jekyll升级到1.2.1,导致jekyll-pagination在jekyll build失败的问题,处理方法参考<a href="https://github.com/blackwinter/jekyll-pagination/issues/3" target="_blank" rel="noopener">https://github.com/blackwinter/jekyll-pagination/issues/3</a>。我是在安装jekyll-pagination 0.0.4后,手动到目录修改它的pagination.rb代码为它在github上的最新代码即可。</del></p><p> //2014.8.25</p><p> <del>因为jekyll升级到2.3.0后,自带一个<code>jekyll-paginate</code>,所以不再需要<code>jekyll-pagination</code></del></p><p> //2015.6.25</p><p> jekyll升级到2.5.3后,<code>sass</code>也不再需要了</p></li><li><p>按需更改</p><ul><li><p>与原作者相关的所有信息</p><p> 404.html、default.html等页面,当然主要是模板页。</p></li><li><p>页面布局,相关代码</p><p> 原作者的blog内容比较多,有些页面我用不到就直接删了;还有原作者写了一些jekll插件,要使用它的就必须在_config.yml中将safe设置为false,因为github不支持自定义插件。</p></li><li><p>google analytics 和 disqus</p><p> 前者用来分析站点的相关信息,和seo有关,要埋上一些代码。后者是第三方的评论系统,在官网注册个账户,同样在网站里埋上一些代码即可。</p></li></ul></li></ol><p><strong>2.3.2 发布自己的blog</strong></p><ol><li>在自己的github上建个项目,并clone到本地。</li><li>按照Bilalh的blog的设计思想,就是开个source分支来写blog的页面代码,再生成静态页面到master,最终push到github。稍等一会儿就可以通过github访问自己blog啦~~</li></ol><h3 id="3-使用篇"><a href="#3-使用篇" class="headerlink" title="3 使用篇"></a>3 使用篇</h3><p>本人能力有限,在此贴出一些有用的站点,权当借鉴参考~~</p><p>1 可以了解jeklly的配置、目录结构意义等。</p><p><a href="https://github.com/mojombo/jekyll" target="_blank" rel="noopener">https://github.com/mojombo/jekyll</a></p><p>2 想自己写插件,还是乖乖学ruby好了。</p><p><a href="http://www.ruby-lang.org/en/" target="_blank" rel="noopener">http://www.ruby-lang.org/en/</a></p><p>3 简洁明了的markdown标记语言。</p><p><a href="http://daringfireball.net/projects/markdown/" target="_blank" rel="noopener">http://daringfireball.net/projects/markdown/</a></p><p>3 推荐写作工具</p><p><a href="http://mouapp.com" target="_blank" rel="noopener">mou</a>:一个所见即所得的markdown编辑器。</p><p><a href="http://bywordapp.com" target="_blank" rel="noopener">byword</a>:markdown编辑器,提供一个简洁的全屏幕打字界面,可以更加专注文字创作。</p><h2 id="后序"><a href="#后序" class="headerlink" title="后序"></a>后序</h2><p>终于成功地在github上搭起了blog,剩下的就需要自己坚持不懈地努力blog啦~~加油>_<</p><h2 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h2><p>1) <a href="https://help.github.com/categories/20/articles" title="github pages help" target="_blank" rel="noopener">github pages help</a> </p><p>2) <a href="http://stackoverflow.com/questions/6414232/curl-certificate-error-when-using-rvm-to-install-ruby-1-9-2" target="_blank" rel="noopener">http://stackoverflow.com/questions/6414232/curl-certificate-error-when-using-rvm-to-install-ruby-1-9-2</a></p><p>3) <a href="http://brandonbohling.com/2011/08/27/Installing-Jekyll-on-Mac/" target="_blank" rel="noopener">http://brandonbohling.com/2011/08/27/Installing-Jekyll-on-Mac/</a></p>]]></content>
<summary type="html">
Blogging with jekyll
</summary>
<category term="Usage" scheme="https://ddrccw.github.io/categories/Usage/"/>
<category term="github" scheme="https://ddrccw.github.io/tags/github/"/>
<category term="github-pages" scheme="https://ddrccw.github.io/tags/github-pages/"/>
<category term="jekyll" scheme="https://ddrccw.github.io/tags/jekyll/"/>
</entry>
<entry>
<title>single app mode</title>
<link href="https://ddrccw.github.io/2012/07/30/2012-07-30-single-app-mode/"/>
<id>https://ddrccw.github.io/2012/07/30/2012-07-30-single-app-mode/</id>
<published>2012-07-30T06:18:00.000Z</published>
<updated>2018-08-11T14:02:42.670Z</updated>
<content type="html"><![CDATA[<p>所谓的single app mode, 就是让iOS设备只运行指定的app,同时让物理的home键失效, 让使用者无法退出相应的app。咋看之下,或许这种模式显得很不符合用户的使用习惯。但具体考虑还是存在适合使用这种mode的情况,如企业用户app,教育类app, 信息展示app或儿童使用的app。</p><p><a href="http://www.apple.com/ios/ios6/" title="iOS 6" target="_blank" rel="noopener">iOS 6</a>已经准备提供Guided Access,其描述如下:</p><blockquote><p>iOS 6 comes with even more features to make it easier for people with vision, hearing, learning, and mobility disabilities to get the most from their iOS devices. Guided Access helps students with disabilities such as autism remain on task and focused on content. It allows a parent, teacher, or administrator to limit an iOS device to one app by disabling the Home button, as well as restrict touch input on certain areas of the screen. VoiceOver, the revolutionary screen reader for blind and low-vision users, is now integrated with Maps, AssistiveTouch, and Zoom. And Apple is working with top manufacturers to introduce Made for iPhone hearing aids that will deliver a power-efficient, high-quality digital audio experience.</p></blockquote><p>那么iOS 6之前的版本按理说应该没有提供这种模式,然而去apple体验店看看其ipad上展示产品信息的app,你会纳闷得发现它确实很像前面提到的single app mode,难道其中有什么玄机,带着这个疑问,我开始在网上搜寻答案。</p><p>经过一番搜索,在万能的stackoverflow上,确实有达人给出了一些线索。</p><h2 id="1-disable-home-button"><a href="#1-disable-home-button" class="headerlink" title="1. disable home button"></a>1. disable home button</h2><p>首先,这里需要了解《应用程序分发和部署问题》中提到的 iphone 配置实用工具(IPCU)以及配置描述文件的概念。</p><p>然后,准备好配置文件, 因为在此主要关注如何disable home button,所以暂且取名为<a href="http://stackoverflow.com/questions/5011774/lock-down-iphone-ipod-ipad-so-it-can-only-run-one-app/8994690#8994690" target="_blank" rel="noopener">disable_home_button.mobileconfig</a>, 内容如下:</p><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><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><?xml version="1.0" encoding="UTF-8"?></span><br><span class="line"><span class="meta"><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"></span></span><br><span class="line"><span class="tag"><<span class="name">plist</span> <span class="attr">version</span>=<span class="string">"1.0"</span>]]></span></span><br><span class="line"><span class="tag"><<span class="name">dict</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">key</span>></span>PayloadContent<span class="tag"></<span class="name">key</span>></span></span><br><span class="line"><span class="tag"><<span class="name">array</span>></span></span><br><span class="line"><span class="tag"><<span class="name">dict</span>></span> </span><br><span class="line"><span class="tag"><<span class="name">key</span>></span>PayloadDescription<span class="tag"></<span class="name">key</span>></span> </span><br><span class="line"><span class="tag"><<span class="name">string</span>></span>Disables home<span class="tag"></<span class="name">string</span>></span></span><br><span class="line"><span class="tag"><<span class="name">key</span>></span>PayloadDisplayName<span class="tag"></<span class="name">key</span>></span></span><br><span class="line"><span class="tag"><<span class="name">string</span>></span>Home Button Lock<span class="tag"></<span class="name">string</span>></span></span><br><span class="line"><span class="tag"><<span class="name">key</span>></span>PayloadIdentifier<span class="tag"></<span class="name">key</span>></span></span><br><span class="line"><span class="tag"><<span class="name">string</span>></span>com.myorg.kiosk<span class="tag"></<span class="name">string</span>></span></span><br><span class="line"><span class="tag"><<span class="name">key</span>></span>PayloadOrganization<span class="tag"></<span class="name">key</span>></span></span><br><span class="line"><span class="tag"><<span class="name">string</span>></span>My Org<span class="tag"></<span class="name">string</span>></span></span><br><span class="line"><span class="tag"><<span class="name">key</span>></span>PayloadType<span class="tag"></<span class="name">key</span>></span> </span><br><span class="line"><span class="tag"><<span class="name">string</span>></span>com.apple.defaults.managed<span class="tag"></<span class="name">string</span>></span></span><br><span class="line"><span class="tag"><<span class="name">key</span>></span>PayloadUUID<span class="tag"></<span class="name">key</span>></span></span><br><span class="line"><span class="tag"><<span class="name">string</span>></span>B2D02E2D-BAC5-431B-8A29-4B91F71C9FC1<span class="tag"></<span class="name">string</span>></span></span><br><span class="line"><span class="tag"><<span class="name">key</span>></span>PayloadVersion<span class="tag"></<span class="name">key</span>></span></span><br><span class="line"><span class="tag"><<span class="name">integer</span>></span>1<span class="tag"></<span class="name">integer</span>></span></span><br><span class="line"><span class="tag"><<span class="name">key</span>></span>PayloadContent<span class="tag"></<span class="name">key</span>></span></span><br><span class="line"><span class="tag"><<span class="name">array</span>></span></span><br><span class="line"><span class="tag"><<span class="name">dict</span>></span> </span><br><span class="line"><span class="tag"><<span class="name">key</span>></span>DefaultsDomainName<span class="tag"></<span class="name">key</span>></span></span><br><span class="line"><span class="tag"><<span class="name">string</span>></span>com.apple.springboard<span class="tag"></<span class="name">string</span>></span></span><br><span class="line"><span class="tag"><<span class="name">key</span>></span>DefaultsData<span class="tag"></<span class="name">key</span>></span></span><br><span class="line"><span class="tag"><<span class="name">dict</span>></span> </span><br><span class="line"><span class="tag"><<span class="name">key</span>></span>SBStoreDemoAppLock<span class="tag"></<span class="name">key</span>></span></span><br><span class="line"><span class="tag"><<span class="name">true</span>/></span> </span><br><span class="line"><span class="tag"></<span class="name">dict</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dict</span>></span></span><br><span class="line"><span class="tag"></<span class="name">array</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dict</span>></span></span><br><span class="line"><span class="tag"></<span class="name">array</span>></span></span><br><span class="line"><span class="tag"><<span class="name">key</span>></span>PayloadDescription<span class="tag"></<span class="name">key</span>></span></span><br><span class="line"><span class="tag"><<span class="name">string</span>></span>Disables Home Button<span class="tag"></<span class="name">string</span>></span></span><br><span class="line"><span class="tag"><<span class="name">key</span>></span>PayloadDisplayName<span class="tag"></<span class="name">key</span>></span> </span><br><span class="line"><span class="tag"><<span class="name">string</span>></span>Home Button Lock<span class="tag"></<span class="name">string</span>></span></span><br><span class="line"><span class="tag"><<span class="name">key</span>></span>PayloadIdentifier<span class="tag"></<span class="name">key</span>></span></span><br><span class="line"><span class="tag"><<span class="name">string</span>></span>com.myorg<span class="tag"></<span class="name">string</span>></span> </span><br><span class="line"><span class="tag"><<span class="name">key</span>></span>PayloadOrganization<span class="tag"></<span class="name">key</span>></span></span><br><span class="line"><span class="tag"><<span class="name">string</span>></span>My Org<span class="tag"></<span class="name">string</span>></span></span><br><span class="line"><span class="tag"><<span class="name">key</span>></span>PayloadType<span class="tag"></<span class="name">key</span>></span></span><br><span class="line"><span class="tag"><<span class="name">string</span>></span>Configuration<span class="tag"></<span class="name">string</span>></span></span><br><span class="line"><span class="tag"><<span class="name">key</span>></span>PayloadUUID<span class="tag"></<span class="name">key</span>></span> </span><br><span class="line"><span class="tag"><<span class="name">string</span>></span>614D1FE3-F80D-4643-AF6B-D10C4CC8737A<span class="tag"></<span class="name">string</span>></span></span><br><span class="line"><span class="tag"><<span class="name">key</span>></span>PayloadVersion<span class="tag"></<span class="name">key</span>></span> </span><br><span class="line"><span class="tag"><<span class="name">integer</span>></span>1<span class="tag"></<span class="name">integer</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dict</span>></span></span><br><span class="line"><span class="tag"></<span class="name">plist</span>></span></span><br></pre></td></tr></table></figure><p>由于该配置文件中包含有IPCU不能处理的payload项,所以配置文件的部署要通过其他途径,较为可行的是《应用程序分发和部署问题》中提到的 over-the-air 分发profile and configuration。</p><p>ps:</p><p>1 具体的项目中,为了防止配置项被移除,最好通过加密只允许授权移除。具体的加密相关payload项,参考<a href="http://manuals.info.apple.com/en_US/Enterprise_Deployment_Guide.pdf" title="企业开发部署指南" target="_blank" rel="noopener">Enterprise_Deployment_Guide</a></p><p>2 移除该配置描述文件,需要通过IPCU,或重启进入General再移除</p><p>最后,安装mobileconfig后,需要重新启动设备,在运行相应的app。</p><p>注意:上述步骤只是保证了运行一个app后,无法通过home键回到springboard。所以部署时,不要运行不相关的app。一旦运行错了,需要重新启动,进行相关操作。</p><h2 id="2-app-crash的处理"><a href="#2-app-crash的处理" class="headerlink" title="2. app crash的处理"></a>2. app crash的处理</h2><h3 id="2-1-可行的方案探讨"><a href="#2-1-可行的方案探讨" class="headerlink" title="2.1 可行的方案探讨"></a>2.1 可行的方案探讨</h3><p>一中提到的方法,只是disable home button,而app crash的情况毕竟在所难免。就算经过一的处理,app crash后仍然能回到springboard。所以为了保证,始终运行一个app,需要寻求一种方案,在app crash之后能重新启动app。其中就需要了解custom url scheme。利用一个restart app来<a href="http://developer.apple.com/library/ios/#documentation/iphone/conceptual/iphoneosprogrammingguide/AdvancedAppTricks/AdvancedAppTricks.html#//apple_ref/doc/uid/TP40007072-CH7-SW50" title="app开发高级技巧" target="_blank" rel="noopener">重启</a>crash 的app。</p><p><img src="/images/single-app-mode.png" alt="alt illustration" title="crash 处理概图"></p><blockquote><p>Armed with this simple idea, the implementation is straightforward:</p><ol><li>Register the “restart” protocol in the Trampoline app.</li><li><p>In the Kiosk app’s crash handler, launch the restarter:</p><p> [sharedApp openURL:[NSURL URLWithString:@”restart://“]]</p></li><li><p>In the Trampoline app (e.g. in -viewDidAppear:), relaunch the crashed app:</p><p> [sharedApp openURL:[NSURL URLWithString:@”mykioskapp://“]]</p></li></ol></blockquote><h3 id="2-2-crash的捕捉"><a href="#2-2-crash的捕捉" class="headerlink" title="2.2 crash的捕捉"></a>2.2 crash的捕捉</h3><p><strong>//todo</strong></p><hr><h2 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献:"></a>参考文献:</h2><p>1) <a href="http://www.apple.com/ios/ios6/" title="iOS 6" target="_blank" rel="noopener">iOS 6的介绍</a></p><p>2) <a href="http://manuals.info.apple.com/en_US/Enterprise_Deployment_Guide.pdf" title="企业开发部署指南" target="_blank" rel="noopener">企业开发部署指南</a></p><p>3) <a href="http://developer.apple.com/library/ios/#documentation/iphone/conceptual/iphoneosprogrammingguide/AdvancedAppTricks/AdvancedAppTricks.html#//apple_ref/doc/uid/TP40007072-CH7-SW50" title="app开发高级技巧" target="_blank" rel="noopener">app开发高级技巧</a></p><p>4) <a href="http://stackoverflow.com/questions/5011774/lock-down-iphone-ipod-ipad-so-it-can-only-run-one-app/8994690#8994690" target="_blank" rel="noopener">http://stackoverflow.com/questions/5011774/lock-down-iphone-ipod-ipad-so-it-can-only-run-one-app/8994690#8994690</a></p><p>5) <a href="http://zchristopoulos.com/2012/02/how-to-disable-ipad-home-button-kioskstore-demo-mode/" target="_blank" rel="noopener">http://zchristopoulos.com/2012/02/how-to-disable-ipad-home-button-kioskstore-demo-mode/</a></p><p>6) <a href="https://blog.compeople.eu/apps/?p=275" target="_blank" rel="noopener">https://blog.compeople.eu/apps/?p=275</a></p>]]></content>
<summary type="html">
single app mode
</summary>
<category term="iOS-dev" scheme="https://ddrccw.github.io/categories/iOS-dev/"/>
<category term="iOS" scheme="https://ddrccw.github.io/tags/iOS/"/>
<category term="IPCU" scheme="https://ddrccw.github.io/tags/IPCU/"/>
</entry>
<entry>
<title>获取Settings bundle中预定义的默认值</title>
<link href="https://ddrccw.github.io/2012/06/20/2012-06-20-register-setting-bundle-default-value/"/>
<id>https://ddrccw.github.io/2012/06/20/2012-06-20-register-setting-bundle-default-value/</id>
<published>2012-06-20T07:18:00.000Z</published>
<updated>2018-08-11T09:43:45.736Z</updated>
<content type="html"><![CDATA[<h2 id="1-背景"><a href="#1-背景" class="headerlink" title="1 背景"></a>1 背景</h2><p>所谓的Settings Bundle就是把一个名为Settings.bundle的文件放置于你的app的根目录下。该文件可以被系统的Settings.app所用,来配置你自己的app的一些设置。<br>具体介绍参考<a href="http://developer.apple.com/library/ios/#DOCUMENTATION/Cocoa/Conceptual/UserDefaults/Preferences/Preferences.html#//apple_ref/doc/uid/10000059i-CH6-SW5" target="_blank" rel="noopener">Implementing an iOS Settings Bundle</a></p><h2 id="2-问题描述"><a href="#2-问题描述" class="headerlink" title="2 问题描述"></a>2 问题描述</h2><p>理想情况下,我们希望app安装完好,然后直接进入能载入一些相关配置,如果有默认配置值,则直接加以应用。<br>然而开发过程中,在xcode中设置setting bundle里一些配置项的默认值后,运行app,通过<code>[[NSUserDefaults standardUserDefaults] objectForKey:@"demokey"]</code>的方法(注:在setting.app中设置的值是可以通过该方法获取的),却获取不了先前预定义的默认值。虽然在setting.app中可以看到设置的默认值。</p><h2 id="3-解决方法"><a href="#3-解决方法" class="headerlink" title="3 解决方法"></a>3 解决方法</h2><p>经过一番查询,可以得出结论目前ios sdk (<= 5.1.1)未提供直接获取默认值的方法,需要自己实现。如下:</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><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)registerDefaultsFromSettingsBundle {</span><br><span class="line"> [[<span class="built_in">NSUserDefaults</span> standardUserDefaults] registerDefaults:[<span class="keyword">self</span> defaultsFromPlistNamed:<span class="string">@"Root"</span>]];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="built_in">NSDictionary</span> *)defaultsFromPlistNamed:(<span class="built_in">NSString</span> *)plistName {</span><br><span class="line"> <span class="built_in">NSString</span> *settingsBundle = [[<span class="built_in">NSBundle</span> mainBundle] pathForResource:<span class="string">@"Settings"</span> ofType:<span class="string">@"bundle"</span>];</span><br><span class="line"> <span class="built_in">NSAssert</span>(settingsBundle, <span class="string">@"Could not find Settings.bundle while loading defaults."</span>);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">NSString</span> *plistFullName = [<span class="built_in">NSString</span> stringWithFormat:<span class="string">@"%@.plist"</span>, plistName];</span><br><span class="line"></span><br><span class="line"> <span class="built_in">NSDictionary</span> *settings = [<span class="built_in">NSDictionary</span> dictionaryWithContentsOfFile:[settingsBundle stringByAppendingPathComponent:plistFullName]];</span><br><span class="line"> <span class="built_in">NSAssert1</span>(settings, <span class="string">@"Could not load plist '%@' while loading defaults."</span>, plistFullName);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">NSArray</span> *preferences = [settings objectForKey:<span class="string">@"PreferenceSpecifiers"</span>];</span><br><span class="line"> <span class="built_in">NSAssert1</span>(preferences, <span class="string">@"Could not find preferences entry in plist '%@' while loading defaults."</span>, plistFullName);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">NSMutableDictionary</span> *defaults = [<span class="built_in">NSMutableDictionary</span> dictionary];</span><br><span class="line"> <span class="keyword">for</span>(<span class="built_in">NSDictionary</span> *prefSpecification <span class="keyword">in</span> preferences) {</span><br><span class="line"> <span class="built_in">NSString</span> *key = [prefSpecification objectForKey:<span class="string">@"Key"</span>];</span><br><span class="line"><span class="keyword">if</span>(key){</span><br><span class="line"><span class="keyword">id</span> value = [prefSpecification objectForKey:<span class="string">@"DefaultValue"</span>];</span><br><span class="line"><span class="keyword">if</span>(value) {</span><br><span class="line">[defaults setObject:value forKey:key];</span><br><span class="line">} </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"> <span class="built_in">NSString</span> *type = [prefSpecification objectForKey:<span class="string">@"Type"</span>];</span><br><span class="line"> <span class="keyword">if</span> ([type isEqualToString:<span class="string">@"PSChildPaneSpecifier"</span>]) {</span><br><span class="line"> <span class="built_in">NSString</span> *file = [prefSpecification objectForKey:<span class="string">@"File"</span>];</span><br><span class="line"> <span class="built_in">NSAssert1</span>(file, <span class="string">@"Unable to get child plist name from plist '%@'"</span>, plistFullName);</span><br><span class="line"> [defaults addEntriesFromDictionary:[<span class="keyword">self</span> defaultsFromPlistNamed:file]];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> defaults;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>说明</strong></p><p>上述方法支持从setting bundle中获取配置的默认值,并且setting bundle在setting.app的main panel中可以包含多个child panel。<br>只需要在<br><code>- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions</code><br>中调用一次,随后就可以直接利用NSUserDefaults获取默认值。<br>相关讨论具体参看<a href="http://stackoverflow.com/questions/510216/can-you-make-the-settings-in-settings-bundle-default-even-if-you-dont-open-the" target="_blank" rel="noopener">stackoverflow</a></p>]]></content>
<summary type="html">
所谓的Settings Bundle就是把一个名为Settings.bundle的文件放置于你的app的根目录下。该文件可以被系统的Settings.app所用,来配置你自己的app的一些设置。
</summary>
<category term="iOS-dev" scheme="https://ddrccw.github.io/categories/iOS-dev/"/>
<category term="iOS" scheme="https://ddrccw.github.io/tags/iOS/"/>
<category term="Settings.bundle" scheme="https://ddrccw.github.io/tags/Settings-bundle/"/>
</entry>
</feed>