-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
489 lines (286 loc) · 274 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Calios' Eden</title>
<icon>https://www.gravatar.com/avatar/19f6cc2f0830c433fe726863dc8fde62</icon>
<subtitle>Stedfast as thou art.</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://www.caliosd.gq/"/>
<updated>2020-03-25T06:23:10.771Z</updated>
<id>http://www.caliosd.gq/</id>
<author>
<name>Calios</name>
<email>[email protected]</email>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>记一次线上故障处理及思考</title>
<link href="http://www.caliosd.gq/2020/03/22/issue-reflect-200322/"/>
<id>http://www.caliosd.gq/2020/03/22/issue-reflect-200322/</id>
<published>2020-03-22T10:15:45.000Z</published>
<updated>2020-03-25T06:23:10.771Z</updated>
<content type="html"><![CDATA[<p>下午正在翻看刚刚读过的一本书,业务群里报了个线上的问题,紧急修复了之后测试、部署上线。<br>乍一看,出现问题的点在一个长达 171 行的方法中,出错的原因是在对一个 map 变量操作之前没有正确赋值。但仔细看来,这段代码已经开始有些坏味道了。<br>核心的问题是:<strong>逻辑复杂,方法过长</strong>。</p><a id="more"></a><p>从业务逻辑上来看,这个方法在修改订单时批量更新或删除商品,并更新订单总价,入参为订单号、商品数组、修改/删除的商品类别(分为普通商品、赠品和套餐三类)。在一个事务中处理如下几件事:</p><ul><li>创建事务</li><li>根据订单号获取当前订单的订单-商品数组;</li><li>遍历获取的不同类别的商品,存入相应的 map 中,以备计算总价格;</li><li>若入参数组为空且类别为套餐,则删除订单中的该套餐及其中商品;</li><li>遍历请求的商品数组:<ul><li>若在订单中已存在且有变:数量变为0,则删除该条记录;数量不为0,则更新该条记录</li><li>若在订单中不存在,则添加一条记录</li></ul></li><li>开始计算总价:<ul><li>若为普通商品:累加 商品单价 * 数量</li><li>若为套餐:累加套餐售价</li><li>若有某种特定优惠券:减 10 元</li><li>根据是否首单、是否包含某种特定套餐计算邮费</li></ul></li><li>更新订单总价</li><li>更新合伙人佣金</li><li>提交事务</li></ul><p>可以看到这的确是一个很长的流程,所以小两百行的代码就可以理解了。但过长的代码难免会带来一些问题,比如逻辑不清晰、看了后面忘了前面、临时变量定义和使用距离太远等等。<br>所以,最好是将整片的代码切成合适的小块,将流程中的每一部分都抽离成一个独立的方法,在主方法中只保留调用的入口,这样会看起来清晰很多。</p><p>想起之前读过的<a href="https://markhneedham.com/blog/2008/09/15/clean-code-book-review/" target="_blank" rel="external">一篇《Clean Code》书评</a>中的一段:</p><blockquote><p>The best idea in this book for me was the <strong>newspaper metaphor</strong> that is mentioned with regards to formatting your code. This describes the idea of making code read like a newspaper article. We should be able to get a general idea of how it works near the top of the class before reading more and more details further down. This can be achieved by breaking the code out into lots of small methods. It was strange how involved I got with the newspaper metaphor. Having read about it early on I started looking at all code after that to be in that format and when it wasn’t (when showing examples of not such clean code) I became disappointed.</p></blockquote><p>将代码比作新闻报道的确精巧:文章各章节的黑字标题就是主方法中的子方法调用,读过去一气呵成,对于关心的细节再 step into 子方法中查看。这样不仅保持了代码逻辑的连贯,也减轻的读代码人的精神压力:P。</p><p>按照这个思路,上述的主方法可以优化为:</p><ul><li>创建事务</li><li>准备订单已有不同类别商品的 map</li><li>按需删除订单中的套餐及商品</li><li>遍历和更新请求的商品数组</li><li>计算总价格并更新</li><li>更新合伙人佣金</li><li>提交事务</li></ul><p>将上述每一步都抽取成一个子方法,主方法可以精简到百行左右,而各子方法各司其职,既符合了单一职责原则,也具有了更好的可读性,甚至具有了更好的可测试性(可以单独测试其中个别复杂方法,比如价格计算)。<br>反过来想想,在编写代码的时候也可以用这样的思路:先逐步写出主方法的 guideline,不存在的方法先简单添加方法定义,完成主方法后再逐个填充子方法,这样更容易保证流程的完整,也方便每次集中处理一个小方法。<br>当然,行数并不是衡量代码质量的唯一标准,<strong>写出可读、可扩展、可测试的代码才是我们的最终目的</strong>。</p><hr><p><strong>后续:</strong></p><p>对于这种逻辑复杂的核心方法,添加对应的测试是个保证代码质量的好办法,比一点点手工黑盒测试不知道要高效多少倍。下次试试。</p>]]></content>
<summary type="html">
<p>下午正在翻看刚刚读过的一本书,业务群里报了个线上的问题,紧急修复了之后测试、部署上线。<br>乍一看,出现问题的点在一个长达 171 行的方法中,出错的原因是在对一个 map 变量操作之前没有正确赋值。但仔细看来,这段代码已经开始有些坏味道了。<br>核心的问题是:<strong>逻辑复杂,方法过长</strong>。</p>
</summary>
<category term="CaseReview" scheme="http://www.caliosd.gq/tags/CaseReview/"/>
</entry>
<entry>
<title>Go 的反射</title>
<link href="http://www.caliosd.gq/2020/03/15/go-reflect/"/>
<id>http://www.caliosd.gq/2020/03/15/go-reflect/</id>
<published>2020-03-15T08:39:37.000Z</published>
<updated>2020-03-17T04:05:26.533Z</updated>
<content type="html"><![CDATA[<h2 id="1-概述"><a href="#1-概述" class="headerlink" title="1.概述"></a>1.概述</h2><p>什么是反射?反射是程序能够查看自身结构、类型、甚至能够操纵自身的能力,是元编程的一种形式。</p><p>我们说某个语言具有元编程的能力,通常分成两类:</p><ul><li>一类是宏,即在编译期生成代码,比如C;</li><li>另一类是在运行时修改代码的行为,比如 Objective-C,比如 Golang。</li></ul><p>Go 通过 reflect 库提供了在运行时操控数据的能力,就是后面的一种。本篇将结合 reflect 库来讲解 Go 是如何提供反射的功能、并为我们的开发提供便利的。</p><a id="more"></a><h2 id="2-核心的Value和Type"><a href="#2-核心的Value和Type" class="headerlink" title="2.核心的Value和Type"></a>2.核心的Value和Type</h2><p>reflect包中关键的两个类型:Type和Value,通过 reflect.TypeOf 和 reflect.ValueOf 可以获取到对应的 Type 和 Value。</p><p><img src="/images/reflect01.png" alt="type-and-value"></p><p>Type表示一个Go的类型,在包中被定义成了一个接口,接口中定义了一些实用的方法,比如 <code>MethodByName</code> 可以获取当前类型对应方法的引用等等。<br><figure class="highlight elm"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">type</span> <span class="type">Type</span> interface {</div><div class="line"> <span class="type">Align</span>() int</div><div class="line"> <span class="type">FieldAlign</span>() int</div><div class="line"> <span class="type">Method(int)</span> <span class="type">Method</span></div><div class="line"> <span class="type">MethodByName(string)</span> (<span class="type">Method</span>, bool)</div><div class="line"> <span class="type">NumMethod</span>() int</div><div class="line"> ...</div><div class="line"> <span class="type">Implements</span>(u <span class="type">Type</span>) bool</div><div class="line"> ...</div><div class="line">}</div></pre></td></tr></table></figure></p><p>Value,表示一个Go的值,它被声明成了一个结构体,并提供了获取或写入的方法。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">type <span class="type">Value</span> <span class="class"><span class="keyword">struct</span> </span>{</div><div class="line"> <span class="comment">// contains filtered or unexported fields</span></div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(v Value)</span></span> <span class="type">Addr</span>() <span class="type">Value</span></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(v Value)</span></span> <span class="type">Bool</span>() bool</div><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(v Value)</span></span> <span class="type">Bytes</span>() []byte</div></pre></td></tr></table></figure><p>Value,二者均可用==来比较,都提供了Kind()方法来表示存储的是哪一类数据,比如Slice、String、Struct等。reflect包提供的方法有两点值得注意:</p><ul><li>包中提供的 Value类型的 “getter”、“setter”方法,操作的都是能够持有值的最大的类型,比如 <code>func (v Value) SetInt(x int64)</code> 和 <code>func (v Value) Int() int64</code> 这一对方法, 返回值和参数都是 int64,以便转成使用者需要的类型。</li><li>反射对象的Kind属性描述的是潜在的类型,而不是静态类型。如用户自定义 int 类型的 MyInt 类型,调用 reflect.ValueOf(y).Kind() 得到的是 int,而不是 MyInt。</li></ul><h2 id="3-reflect的三法则"><a href="#3-reflect的三法则" class="headerlink" title="3.reflect的三法则"></a>3.reflect的三法则</h2><p><img src="/images/reflect02.png" alt="flow"></p><h3 id="法则一:从interface值可以反射出反射对象"><a href="#法则一:从interface值可以反射出反射对象" class="headerlink" title="法则一:从interface值可以反射出反射对象"></a>法则一:从interface值可以反射出反射对象</h3><p>调用reflect.TypeOf()时,传入的参数首先会被存在一个空的interface中,然后再作为参数继续传递,TypeOf会解开空interface,并返回还原出的类型信息。</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line">func TypeOf(i interface{})<span class="built_in"> Type </span>{</div><div class="line"> eface := *(<span class="number">*e</span>mptyInterface)(unsafe.Pointer(&i))</div><div class="line"> return toType(eface.typ)</div><div class="line">}</div><div class="line"></div><div class="line">func toType(t *rtype)<span class="built_in"> Type </span>{</div><div class="line"> <span class="keyword">if</span> t == <span class="literal">nil</span> {</div><div class="line"> return <span class="literal">nil</span></div><div class="line"> }</div><div class="line"> return t</div><div class="line">}</div></pre></td></tr></table></figure><p>通过查看 <code>TypeOf</code> 的实现可以看到,即使我们调用 <code>reflect.TypeOf(123)</code>,传入的int类型也会先被转换成 interface 类型的值,所以说,是从interface的值反射出的反射对象。</p><p>interface类型中,有个很重要的例子:空的interface(<code>interface{}</code>)。它代表着空的一组方法,任何值都可以被认为实现了它,因为任何值都肯定有0个或者是多个方法。这也就说明了为什么任何值都可以转换成 interface{} 并传给 TypeOf 方法了。</p><p>实际上,一个 interface 类型的变量中会存储一对数据:赋值给变量的具体的值,和这个值的类型描述。具体地说,这个值就是实际上实现了interface的底层具体数据,类型描述描述的就是这个这个值的完整类型。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// emptyInterface is the header for an interface{} value.</span></div><div class="line"><span class="class"><span class="keyword">type</span> <span class="title">emptyInterface</span></span> <span class="class"><span class="keyword">struct</span> {</span></div><div class="line"> typ *rtype <span class="comment">// 表示变量的类型</span></div><div class="line"> word <span class="keyword">unsafe</span>.Pointer <span class="comment">// 指向内部封装的数据</span></div><div class="line">}</div></pre></td></tr></table></figure><h3 id="法则二:从反射对象可以反射出到interface值"><a href="#法则二:从反射对象可以反射出到interface值" class="headerlink" title="法则二:从反射对象可以反射出到interface值"></a>法则二:从反射对象可以反射出到interface值</h3><p>给定一个reflect.Value,我们可以用Interface方法恢复成一个interface值,实际上就是把类型和值的信息打包成一个interface的结构,并返回结果。<br><figure class="highlight makefile"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">z := 3.14</div><div class="line">v = reflect.ValueOf(z)</div><div class="line">fmt.Println(v.Interface().(float64)) // 打印出类型为float64的3.14</div></pre></td></tr></table></figure></p><p>这就解释了为什么可以对于一个 interface 类型的对象直接调用 xxx.(float64),或者是对于 interface 类型的对象像下面这样 switch,因为 xxx.(type) 就相当于剥开了interface、拿到了里面潜在的类型。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">switch</span> f := arg.(<span class="keyword">type</span>) { <span class="comment">// arg是interface类型的对象</span></div><div class="line"><span class="keyword">case</span> <span class="keyword">bool</span>:</div><div class="line"> ...</div><div class="line"><span class="keyword">case</span> <span class="keyword">int64</span>:</div><div class="line"> ...</div><div class="line">}</div></pre></td></tr></table></figure><p>我们可以通过查看 <code>Interface()</code> 方法的实现来确认:<br><figure class="highlight go"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(v Value)</span> <span class="title">Interface</span><span class="params">()</span> <span class="params">(i <span class="keyword">interface</span>{})</span></span> {</div><div class="line"> <span class="keyword">return</span> valueInterface(v, <span class="literal">true</span>)</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">valueInterface</span><span class="params">(v Value, safe <span class="keyword">bool</span>)</span> <span class="title">interface</span></span>{} {</div><div class="line"> ... some checks ...</div><div class="line"></div><div class="line"> <span class="keyword">if</span> v.kind() == Interface {</div><div class="line"> <span class="comment">// Special case: return the element inside the interface.</span></div><div class="line"> <span class="comment">// Empty interface has one layout, all interfaces with</span></div><div class="line"> <span class="comment">// methods have a second layout.</span></div><div class="line"> <span class="keyword">if</span> v.NumMethod() == <span class="number">0</span> {</div><div class="line"> <span class="keyword">return</span> *(*<span class="keyword">interface</span>{})(v.ptr)</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> *(*<span class="keyword">interface</span> {</div><div class="line"> M()</div><div class="line"> })(v.ptr)</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="comment">// <span class="doctag">TODO:</span> pass safe to packEface so we don't need to copy if safe==true?</span></div><div class="line"> <span class="keyword">return</span> packEface(v)</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// packEface converts v to the empty interface.</span></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">packEface</span><span class="params">(v Value)</span> <span class="title">interface</span></span>{} {</div><div class="line"> t := v.typ</div><div class="line"> <span class="keyword">var</span> i <span class="keyword">interface</span>{}</div><div class="line"> e := (*emptyInterface)(unsafe.Pointer(&i))</div><div class="line"> <span class="comment">// First, fill in the data portion of the interface.</span></div><div class="line"> ... some logic ...</div><div class="line"> e.word = xxx</div><div class="line"></div><div class="line"> e.typ = t</div><div class="line"> <span class="keyword">return</span> i</div><div class="line">}</div></pre></td></tr></table></figure></p><h3 id="法则三:如果要修改反射对象,它的值必须是可被更新的(settable)"><a href="#法则三:如果要修改反射对象,它的值必须是可被更新的(settable)" class="headerlink" title="法则三:如果要修改反射对象,它的值必须是可被更新的(settable)"></a>法则三:如果要修改反射对象,它的值必须是可被更新的(settable)</h3><p>什么是“可被更新的”?我们先来看一个例子:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> x <span class="keyword">float64</span> = <span class="number">3.4</span></div><div class="line">v := reflect.ValueOf(x)</div><div class="line">v.SetFloat(<span class="number">7.1</span>) <span class="comment">// Error: will panic.</span></div></pre></td></tr></table></figure><p>执行上面的代码会panic,并给出错误信息:</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">panic: reflect<span class="selector-class">.Value</span><span class="selector-class">.SetFloat</span> using unaddressable value</div></pre></td></tr></table></figure><p>我们可以使用 CanSet 方法来检查一个 Value 是否是可以被更新的:<br>var x float64 = 3.4<br>v := reflect.ValueOf(x)<br>fmt.Println(“settability of v:”, v.CanSet())<br>// output: settability of v: false</p><p>“可被更新的”,是指反射对象能改变创建反射对象的实际存储空间,它取决于反射对象是否持有原始的数据。比如上面panic的代码段,由于 Go 语言的函数调用都是传值的,所以我们得到的反射对象跟最开始的变量没有任何关系,所以直接去修改它会导致崩溃。想要修改原有的变量只能通过如下方法:<br><figure class="highlight go"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> x <span class="keyword">float64</span> = <span class="number">3.4</span></div><div class="line">v := reflect.ValueOf(&x) <span class="comment">// 获取变量指针对应的 reflect.Value</span></div><div class="line">v.Elem().SetFloat(<span class="number">7.1</span>) <span class="comment">// 获取指针指向的变量,更新变量的值</span></div><div class="line">fmt.Println(i)</div></pre></td></tr></table></figure></p><h2 id="4-常见应用"><a href="#4-常见应用" class="headerlink" title="4.常见应用"></a>4.常见应用</h2><h3 id="a-动态获取某个struct的属性和属性值"><a href="#a-动态获取某个struct的属性和属性值" class="headerlink" title="a.动态获取某个struct的属性和属性值"></a>a.动态获取某个struct的属性和属性值</h3><h3 id="b-field-tag,通过添加-tag-对于指定的属性做额外处理"><a href="#b-field-tag,通过添加-tag-对于指定的属性做额外处理" class="headerlink" title="b.field tag,通过添加 tag 对于指定的属性做额外处理"></a>b.field tag,通过添加 tag 对于指定的属性做额外处理</h3><ul><li>json parser/encoder:<code>json:”id"</code></li><li>gorm/xorm:<code>gorm:”unique_index(code_idx)"</code></li><li>validater:<code>validate:"max_length:250"</code><figure class="highlight stylus"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div></pre></td><td class="code"><pre><div class="line">func TestReflectStructTag(t *testing.T) {</div><div class="line"> type S struct {</div><div class="line"> F string `species:<span class="string">"gopher"</span> <span class="attribute">color</span>:<span class="string">"blue"</span>`</div><div class="line"> e string `species:<span class="string">"dolphin"</span> <span class="attribute">color</span>:<span class="string">"lilac"</span>`</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="comment">// v1</span></div><div class="line"> s := &S{}</div><div class="line"> st := reflect.TypeOf(s).Elem() <span class="comment">// <- 通过Elem() 获取指针真正指向的类型,如果参数不是指针/slice/map,会panic</span></div><div class="line"> <span class="comment">// v2</span></div><div class="line"> <span class="comment">// s := S{}</span></div><div class="line"> <span class="comment">// st := reflect.TypeOf(s)</span></div><div class="line"> field := st.Field(<span class="number">0</span>)</div><div class="line"></div><div class="line"> fmt.Printf(<span class="string">"st: %v, field: %+v\n"</span>, st, field)</div><div class="line"> fmt.Println(field<span class="selector-class">.Tag</span><span class="selector-class">.Get</span>(<span class="string">"color"</span>), field<span class="selector-class">.Tag</span><span class="selector-class">.Get</span>(<span class="string">"species"</span>))</div><div class="line"> field = st.Field(<span class="number">1</span>)</div><div class="line"></div><div class="line"> fmt.Printf(<span class="string">"field: %+v\n"</span>, field)</div><div class="line"> fmt.Println(field<span class="selector-class">.Tag</span><span class="selector-class">.Get</span>(<span class="string">"color"</span>), field<span class="selector-class">.Tag</span><span class="selector-class">.Get</span>(<span class="string">"species"</span>))</div><div class="line">}</div></pre></td></tr></table></figure></li></ul><h3 id="c-implements,检查某种类型是否实现了某interface"><a href="#c-implements,检查某种类型是否实现了某interface" class="headerlink" title="c.implements,检查某种类型是否实现了某interface"></a>c.implements,检查某种类型是否实现了某interface</h3><figure class="highlight routeros"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div></pre></td><td class="code"><pre><div class="line">func TestReflectImpl(t *testing.T) {</div><div class="line"> err := CustomValidate(&PaymentTransaction{})</div><div class="line"> fmt.Printf(<span class="string">"err: %v\n"</span>, err)</div><div class="line">}</div><div class="line"><span class="built_in"></span></div><div class="line">type Validator<span class="built_in"> interface </span>{</div><div class="line"> Validate() <span class="builtin-name">error</span></div><div class="line">}</div><div class="line"><span class="built_in"></span></div><div class="line">type PaymentTransaction struct {}</div><div class="line"></div><div class="line">func (p *PaymentTransaction) Validate() <span class="builtin-name">error</span> {</div><div class="line"> fmt.Println(<span class="string">"Validating payment transaction"</span>)</div><div class="line"> return <span class="literal">nil</span></div><div class="line">}</div><div class="line"></div><div class="line">func CustomValidate(obj interface{}) <span class="builtin-name">error</span> {</div><div class="line"> v := reflect.ValueOf(obj)</div><div class="line"> t := v.Type()</div><div class="line"> fmt.Println(<span class="string">"ValueOf: "</span>, t, <span class="string">", TypeOf: "</span>, reflect.TypeOf(obj))</div><div class="line"></div><div class="line"> interfaceT := reflect.TypeOf((*Validator)(<span class="literal">nil</span>)).Elem()</div><div class="line"> <span class="keyword">if</span> !t.Implements(interfaceT) {</div><div class="line"> return fmt.Errorf(<span class="string">"The Validator interface is not implemented"</span>)</div><div class="line"> }</div><div class="line"></div><div class="line"> validateFunc := v.MethodByName(<span class="string">"Validate"</span>)</div><div class="line"> validateFunc.Call(<span class="literal">nil</span>) </div><div class="line"> return <span class="literal">nil</span></div><div class="line">}</div></pre></td></tr></table></figure><h2 id="5-如何降低反射带来的性能影响"><a href="#5-如何降低反射带来的性能影响" class="headerlink" title="5.如何降低反射带来的性能影响"></a>5.如何降低反射带来的性能影响</h2><ul><li><a href="https://github.com/mailru/easyjson" target="_blank" rel="external">easyjson</a>,<a href="https://github.com/json-iterator/go" target="_blank" rel="external">jsonitor-go</a>:空间换时间,编译期甚至开发时就人工生成好所需的代码,提高运行时的速度</li><li><a href="https://github.com/modern-go/reflect2" target="_blank" rel="external">reflect2</a>:通过缓存name-offset的map,降低每次调用的查找时间,避免重复获取reflect.Value</li></ul><h2 id="6-参考文档"><a href="#6-参考文档" class="headerlink" title="6.参考文档"></a>6.参考文档</h2><ul><li><a href="https://golang.org/pkg/reflect/" target="_blank" rel="external">https://golang.org/pkg/reflect/</a></li><li><a href="https://blog.golang.org/laws-of-reflection" target="_blank" rel="external">https://blog.golang.org/laws-of-reflection</a></li><li><a href="http://blog.ralch.com/tutorial/golang-reflection/" target="_blank" rel="external">http://blog.ralch.com/tutorial/golang-reflection/</a></li></ul>]]></content>
<summary type="html">
<h2 id="1-概述"><a href="#1-概述" class="headerlink" title="1.概述"></a>1.概述</h2><p>什么是反射?反射是程序能够查看自身结构、类型、甚至能够操纵自身的能力,是元编程的一种形式。</p>
<p>我们说某个语言具有元编程的能力,通常分成两类:</p>
<ul>
<li>一类是宏,即在编译期生成代码,比如C;</li>
<li>另一类是在运行时修改代码的行为,比如 Objective-C,比如 Golang。</li>
</ul>
<p>Go 通过 reflect 库提供了在运行时操控数据的能力,就是后面的一种。本篇将结合 reflect 库来讲解 Go 是如何提供反射的功能、并为我们的开发提供便利的。</p>
</summary>
<category term="Golang" scheme="http://www.caliosd.gq/tags/Golang/"/>
</entry>
<entry>
<title>Go 的版本管理</title>
<link href="http://www.caliosd.gq/2020/02/23/go-versioning/"/>
<id>http://www.caliosd.gq/2020/02/23/go-versioning/</id>
<published>2020-02-23T02:06:45.000Z</published>
<updated>2020-03-15T09:13:58.695Z</updated>
<content type="html"><![CDATA[<h2 id="Semantic-Versioning-2-0-0"><a href="#Semantic-Versioning-2-0-0" class="headerlink" title="Semantic Versioning 2.0.0"></a>Semantic Versioning 2.0.0</h2><p>在软件管理的版本控制中,存在一个可怕的领域,名为<del>“手冢领域”</del>“依赖地狱”,为了解决这个问题,<a href="https://tom.preston-werner.com/" target="_blank" rel="external">Tom Preston-Werner</a> 提出了一组简单的规则来明确版本号应该如何指定和增加,这个系统就叫做 <a href="https://semver.org/" target="_blank" rel="external">“Semantic Versioning”</a>。</p><a id="more"></a><p>日常开发中,我们的版本号管理通常都遵循该规则,即使用 Major.Minor.Patch 这样 X.Y.Z 格式的,它意味着我们需要:</p><ul><li>当我们对 API 做出不兼容的改动时,增大Major;</li><li>当我们增加了向后兼容的新功能时,增大Minor;</li><li>当我们向后兼容地修复了问题时,增大Patch。</li></ul><p>为了表示这是一个版本号,<a href="https://semver.org/#is-v123-a-semantic-version" target="_blank" rel="external">通常在前面加上一个 “v”</a>,即 “vX.Y.Z”。在 go 中也同样遵循这样的规则,接下来我们具体来看 go 中都是怎样进行版本号管理的。</p><p>简而言之,总结为两点:</p><ul><li>不兼容版本通过 import 不同的 path 来区分;</li><li>兼容的版本遵循“最小版本选择”原则。</li></ul><h2 id="vX-Y-Z-中的-X"><a href="#vX-Y-Z-中的-X" class="headerlink" title="vX.Y.Z 中的 X"></a>vX.Y.Z 中的 <strong>X</strong></h2><p>假设 module A 依赖于 module B 和 C,同时 B 依赖于 module D 的 v1.2.3 版本,而 C 依赖于 D 的 v2.3.4 版本,由于 v2.x.x 不是向后兼容的,所以如果 A 使用 D 的 v2.x.x 版本可能会出错,而如果使用 D 的 v1.x.x 版本可能会导致不能满足 C 的需求。对于这种问题,go 提出了要遵循兼容性原则:</p><blockquote><p>Go 的兼容性原则:如果一个老的package和一个新的package有相同的import路径,那么新的package必须向后兼容老的package。</p></blockquote><p>换句话说,如果 import 使用的 path 相同,就要保证新的版本兼容老的版本;否则,就通过在 import 的 path 中添加版本号的方式来区分两个不兼容的版本。v0、v1不需要添加版本号。</p><p>我们通过两个 go 的常用库的使用来对比一下:</p><ul><li><p>当使用 <a href="https://github.com/gin-gonic/gin/tags" target="_blank" rel="external">gin</a> 时,并不需要在path中添加版本号(当前最新版本v1.5.0)。</p><figure class="highlight xl"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">import</span> <span class="string">"github.com/gin-gonic/gin"</span></div></pre></td></tr></table></figure></li><li><p>当使用 <a href="https://github.com/gobuffalo/packr/tags" target="_blank" rel="external">packr</a> 时,如果想使用新版本,需要在path中添加 v2,来保证使用的是 v2 版本。</p><figure class="highlight xl"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">import</span> <span class="string">"github.com/gobuffalo/packr/v2"</span></div></pre></td></tr></table></figure></li></ul><p>回到上面 ABCD 的问题,我们可以在 module B 使用 D 时:<br><figure class="highlight xl"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">import</span> <span class="string">"github.com/someone/D"</span></div></pre></td></tr></table></figure></p><p>在 module C 使用 D 时:<br><figure class="highlight xl"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">import</span> <span class="string">"github.com/someone/D/v2"</span></div></pre></td></tr></table></figure></p><p>相当于 B 和 C 依赖了两个不同的 module,就不存在依赖版本矛盾的问题了。</p><h2 id="vX-Y-Z-中的-Y和Z"><a href="#vX-Y-Z-中的-Y和Z" class="headerlink" title="vX.Y.Z 中的 Y和Z"></a>vX.Y.Z 中的 <strong>Y和Z</strong></h2><p>假设一个场景,当前开发的 module 只依赖于module A,而 A 依赖 module D 的 v1.0.0,而此时 D 的最新版本是 v1.4.2,go 会如何选择呢?选择 D 的 v1.0.0版本。<br><img src="https://raw.githubusercontent.com/CaliosD/CaliosD.github.io/master/images/mvsA.png" alt="mvsA"></p><p>如果此时,当前开发的 module 增加了对于 module B的依赖,而 B 又依赖 module D 的 v1.1.1 版本,那么 go 最终会选择哪个版本呢?选择 D 的 v1.1.1版本。<br><img src="https://raw.githubusercontent.com/CaliosD/CaliosD.github.io/master/images/mvsB.png" alt="mvsB"></p><p>如果此时,当前开发的 module 增加了对于 module C的依赖,而 C 又依赖 module D 的 v1.3.2 版本,那么 go 最终会选择哪个版本呢?选择 D 的 v1.3.2版本。<br><img src="https://raw.githubusercontent.com/CaliosD/CaliosD.github.io/master/images/mvsC.png" alt="mvsC"></p><p>可以看出,go 每次build会选择依赖的 module 列表中,能够同时满足所有条件的最小版本(而不是最新版本),这也就是最小版本选择算法(Minimal Version Selection,MVS)。</p><p>PS:可以通过如下命令查看已有的 D 的各种版本:<br><figure class="highlight vim"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">go</span> <span class="keyword">list</span> -<span class="keyword">m</span> -versions github.<span class="keyword">com</span>/someone/D</div></pre></td></tr></table></figure></p><h3 id="MVS-解决了什么痛点"><a href="#MVS-解决了什么痛点" class="headerlink" title="MVS 解决了什么痛点"></a>MVS 解决了什么痛点</h3><p>很多其他的依赖管理工具,比如说 NPM,都倾向于默认安装最新版本的依赖。这样可能带来的问题是,如果刚好最新的版本引入了一个 bug,那么就可以能一觉醒来,发现昨天还好好的项目开始鸡飞狗跳了┓( ´∀` )┏。这里有一篇<a href="https://about.sourcegraph.com/blog/the-pain-that-minimal-version-selection-solves" target="_blank" rel="external">sourcegraph的博客</a>记录了他们的痛苦经历。简而言之,NPM的做法,是基于这样的假设:</p><ul><li>所有人都是不会犯错的;</li><li>所有人都理解并且遵循Semantic Versioning;</li><li>全网尽快普及最新版本要比个人项目的稳定性重要。</li></ul><p>而实际上,刚好相反:所有人都会犯大大小小的错;不是所有人都清楚地了解Semantic Versioning;对于项目的开发者而言,保证项目的稳定性要比将依赖包更新到最新版本重要得多。</p><p>相比之下,go 的 MVS 就要友好得多,它会尽量保证最终使用的 module 版本贴近包作者开发所用的依赖关系,也就是所谓的“高保真”。</p><h3 id="MVS-是如何解决的"><a href="#MVS-是如何解决的" class="headerlink" title="MVS 是如何解决的"></a>MVS 是如何解决的</h3><p>对于构建列表的常见操作包括:</p><ul><li>构建当前build的依赖列表</li><li>更新所有 module 到最新版本</li><li>更新某个 module 到指定新版本</li><li>回退某个 module 到指定旧版本</li></ul><p>版本选择问题就是给出这4个操作的定义和算法实现。</p><p>MVS 假定每个模块都声明了自己依赖其他模块的最低版本列表(存在于 go.mod 文件中)。假设模块都遵循上面提到的“兼容性原则”,即任何有相同 import path 的包新版本应该和旧版本一样工作,所以依赖需求只需要给出最低版本,而不是最高版本或是不兼容的更改版本的列表。</p><p>基于以上假设,MVS 给出了上面4个操作的定义:</p><ul><li>构建当前build的依赖列表:使用目标本身的列表开始,逐渐追加每个依赖的构建列表。如果某个 module 在列表中出现了多次,就选择最新的一个版本。直观的图来看一眼,具体可以直接通过递归或者图遍历来实现。</li><li>更新所有 module 到最新版本:构造构建列表,但要读取每个依赖,就好像它请求了最新的module版本一样。</li><li>更新某个 module 到指定新版本:构造未升级的构建列表,然后添加新模块的构建列表,如果某个module出现多次,只保留最新版本。</li><li>回退某个 module 到指定旧版本:回退每个顶级需求的依赖版本,直到该需求的构建列表不再引用降级module的较新版本。</li></ul><p>更加详细的讲解见<a href="https://research.swtch.com/vgo-mvs" target="_blank" rel="external">go 版本系列文章之 MVS</a>。<br>具体的代码实现可以查看 <a href="https://github.com/golang/vgo/tree/master/vendor/cmd/go/internal/mvs" target="_blank" rel="external">vgo 的源码</a>。</p><h2 id="Ref"><a href="#Ref" class="headerlink" title="Ref"></a>Ref</h2><ul><li><a href="https://github.com/golang/go/wiki/Modules" target="_blank" rel="external">https://github.com/golang/go/wiki/Modules</a></li><li><a href="https://deepsource.io/blog/go-modules" target="_blank" rel="external">https://deepsource.io/blog/go-modules</a></li><li><a href="https://semver.org/" target="_blank" rel="external">https://semver.org/</a></li><li><a href="https://research.swtch.com/vgo-mvs" target="_blank" rel="external">https://research.swtch.com/vgo-mvs</a></li></ul>]]></content>
<summary type="html">
<h2 id="Semantic-Versioning-2-0-0"><a href="#Semantic-Versioning-2-0-0" class="headerlink" title="Semantic Versioning 2.0.0"></a>Semantic Versioning 2.0.0</h2><p>在软件管理的版本控制中,存在一个可怕的领域,名为<del>“手冢领域”</del>“依赖地狱”,为了解决这个问题,<a href="https://tom.preston-werner.com/" target="_blank" rel="external">Tom Preston-Werner</a> 提出了一组简单的规则来明确版本号应该如何指定和增加,这个系统就叫做 <a href="https://semver.org/" target="_blank" rel="external">“Semantic Versioning”</a>。</p>
</summary>
<category term="Golang" scheme="http://www.caliosd.gq/tags/Golang/"/>
</entry>
<entry>
<title>Go 的面向对象编程</title>
<link href="http://www.caliosd.gq/2020/01/30/oop-in-golang/"/>
<id>http://www.caliosd.gq/2020/01/30/oop-in-golang/</id>
<published>2020-01-30T07:27:59.000Z</published>
<updated>2020-02-03T01:07:11.813Z</updated>
<content type="html"><![CDATA[<p>在我的印象中,正经而严肃认真地看“面向对象”这个概念,有这么几次:第一次,是大学时老师上课讲的,Car和Taxi,Animal和Dog、Cat,Teacher和Student,如此如此;第二次,是在臧成威老师的 iOS 黑魔法课中,学习如何用 C 语言实现面向对象的各种特性;第三次,应该就是最近,在学习设计模式中遇到最形而上之、又被最广泛使用的编程思想——面向对象。这一次,就从我写后端以来最常用的语言 Go 入手,研究一下它是如何体现面向对象思想、日常开发中又是如何使用 Go 进行面向对象编程的吧。</p><h2 id="使用-Go-如何实现面向对象编程?"><a href="#使用-Go-如何实现面向对象编程?" class="headerlink" title="使用 Go 如何实现面向对象编程?"></a>使用 Go 如何实现面向对象编程?</h2><p>首先,第一个问题是:“Go 是一门面向对象的语言吗?”</p><a id="more"></a><p>好吧,这的确属于常见问题,以至于<a href="https://golang.org/doc/faq#Is_Go_an_object-oriented_language" target="_blank" rel="external">官网的 FAQ 中直接回答了</a>它:</p><blockquote><p>是,也不是。尽管 Go 拥有类型和方法,也允许面向对象风格的编程,但它没有类型的继承。Go 中,“接口”这个概念提供了一种不同的方式,我们认为它更加简单易用,在某种意义上也更通用。当然,也可以通过将某类型嵌入到另一种类型中来实现尽管类似、但并不等同的“子类”。此外,Go 的方法比 C++ 或 Java 更加通用:它们可以为任何类型的数据定义,即使是像 integer 这种内置的、无法拆分的类型。它们并不局限与 struct。<br>还有,Go 中不存在类型的继承,使得它的“对象”比 C++ 或 Java 这些语言都要轻量。</p></blockquote><p>面向对象编程中有两个非常重要而基础的概念:类(class)和对象(object)。在 Go 中,没有类(class)的概念,可以使用结构体(struct)来实现类似的功能。当然,面向对象语言并不是能否实现面向对象编程的充要条件。接下来,我们就来看一下 Go 是如何实现面向对象编程的三/四大特性吧。</p><h3 id="0-抽象"><a href="#0-抽象" class="headerlink" title="0.抽象"></a>0.抽象</h3><p>将“抽象”作为 0,实在是因为它是一个非常通用的设计思想,并没有很强的“特异性”,也并不需要编程语言提供特殊的语法机制来支持,甚至使用“函数”就足矣,因此有时并不被看做面向对象编程的特性之一。所以,这里就不再赘述。</p><h3 id="1-封装"><a href="#1-封装" class="headerlink" title="1.封装"></a>1.封装</h3><p>封装,简而言之,就是将内部信息隐藏起来,对外提供可控制的数据访问。它主要是为了保护内部数据不被随意修改,同时仅暴露有限必要的接口,保障代码的可维护性和易用性。通常来说,各编程语言本身都会提供权限访问控制的语法来支持。比如,Java中的 private、protected、public 关键字,Swift 中的 private、public、fileprivate 关键字等等。</p><p>Go 中没有提供类似语法层面的关键字。在我看来,封装一方面可以通过划分包(package)来实现,通过命名是否大写来决定是否可以被包外访问,这一规则适用于 type、interface、struct 或是 function。例如:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div></pre></td><td class="code"><pre><div class="line">type Foo struct {</div><div class="line">}</div><div class="line"> </div><div class="line">func (f Foo) Foo1() {</div><div class="line"> fmt.Println("Foo1() here")</div><div class="line">}</div><div class="line"> </div><div class="line">func (f Foo) Foo2() {</div><div class="line"> fmt.Println("Foo2() here")</div><div class="line">}</div><div class="line"> </div><div class="line">func (f Foo) foo3() {</div><div class="line"> fmt.Println("foo3() here")</div><div class="line">}</div><div class="line"> </div><div class="line">func NewFoo() *Foo {</div><div class="line"> return &Foo{}</div><div class="line">}</div></pre></td></tr></table></figure><p>如上代码所示,<code>Foo</code> 的创建方法 <code>NewFoo()</code> 和 <code>Foo1()</code>、<code>Foo2()</code> 方法都可以被包外使用,而 <code>foo3()</code> 只能在包内使用:</p><figure class="highlight roboconf"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line">package main</div><div class="line"></div><div class="line"><span class="keyword">import</span> "../foo"</div><div class="line"></div><div class="line">func main() {</div><div class="line"><span class="attribute">f</span> := foo<span class="variable">.NewFoo</span>()</div><div class="line">f<span class="variable">.Foo</span>1()</div><div class="line">f<span class="variable">.Foo</span>2()</div><div class="line">//f<span class="variable">.foo</span>3() <- f<span class="variable">.foo</span>3 undefined (cannot refer to unexported field or method foo<span class="variable">.Foo</span><span class="variable">.foo</span>3)go</div><div class="line">}</div></pre></td></tr></table></figure><p>另一方面,如果不希望将 <code>Foo</code> 类型暴露出来,可以通过 <code>interface</code> 来隐藏:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div></pre></td><td class="code"><pre><div class="line">type Fooer interface {</div><div class="line"> Foo1()</div><div class="line"> Foo2()</div><div class="line">}</div><div class="line"> </div><div class="line">type foo struct {</div><div class="line">}</div><div class="line"> </div><div class="line">func (f foo) Foo1() {</div><div class="line"> fmt.Println("Foo1() here")</div><div class="line">}</div><div class="line"> </div><div class="line">func (f foo) Foo2() {</div><div class="line"> fmt.Println("Foo2() here")</div><div class="line">}</div><div class="line"> </div><div class="line">func NewFoo() Fooer {</div><div class="line"> return &foo{}</div><div class="line">}</div></pre></td></tr></table></figure><h3 id="2-继承"><a href="#2-继承" class="headerlink" title="2.继承"></a>2.继承</h3><p>众所周知(努力不去想道长说这四个字时的语气神态=_=),在面向对象编程中,继承用来表示类之间的 is-a 关系,用来解决代码的复用性问题,通常也需要编程语言提供特殊的语法机制来支持。常见的有单继承和多继承两种模式,而多继承最常见的就是“钻石问题(diamond problem)”(D 继承自 B 和 C,而 B 和 C 又都继承自 A,继承链形成了钻石的形状)。</p><figure class="highlight livescript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"> A</div><div class="line"> / <span class="string">\</span></div><div class="line">B C</div><div class="line"> <span class="string">\</span> /</div><div class="line"> D</div></pre></td></tr></table></figure><p>不同编程语言对此解决方案各不相同:有的语言默认分别遵循各自的继承关系,因此 D 中实际上包含两个独立的 A 对象,如 C++;有的语言设定精巧的规则来处理,如 Python;而像 Objective-C、Swift、Java等语言,只允许继承自单一的基类,就不会有 diamond problem 了。</p><p>而 Go,采取了完全不同的方式。在 Go 中,没有通常意义的“类型的继承”,而是使用了 struct 的嵌套(embed)来实现类似的继承和多继承,同时在编译期检查 diamond problem 并报错。如下例所示,<code>SubFooB</code> 和 <code>SubFooC</code> 两个 struct 都嵌套了 <code>Foo</code>,而 <code>MiniFooD</code> 又同时嵌套了 <code>SubFooB</code> 和 <code>SubFooC</code>,这样就形成了一颗“钻石”:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">type</span> SubFooB <span class="keyword">struct</span> {</div><div class="line">Foo</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewSubFooB</span><span class="params">()</span> *<span class="title">SubFooB</span></span> {</div><div class="line"><span class="keyword">return</span> &SubFooB{}</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s SubFooB)</span> <span class="title">Foo2</span><span class="params">()</span></span> {</div><div class="line">fmt.Println(<span class="string">"Foo2() of B here"</span>)</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s SubFooB)</span> <span class="title">Foo3</span><span class="params">()</span></span> {</div><div class="line">fmt.Println(<span class="string">"Foo3() of B here"</span>)</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">type</span> SubFooC <span class="keyword">struct</span> {</div><div class="line">Foo</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewSubFooC</span><span class="params">()</span> *<span class="title">SubFooC</span></span> {</div><div class="line"><span class="keyword">return</span> &SubFooC{}</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s SubFooC)</span> <span class="title">Foo4</span><span class="params">()</span></span> {</div><div class="line">fmt.Println(<span class="string">"Foo4() of C here"</span>)</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">type</span> MiniFooD <span class="keyword">struct</span> {</div><div class="line">SubFooB</div><div class="line">SubFooC</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewMiniFooD</span><span class="params">()</span> *<span class="title">MiniFooD</span></span> {</div><div class="line"><span class="keyword">return</span> &MiniFooD{}</div><div class="line">}</div></pre></td></tr></table></figure><p>此时,如果调用 <code>MiniFooD</code> 的 <code>Foo1()</code> 方法,编译器会直接报错:“ambiguous selector mf.Foo1”,可以通过在 <code>MiniFooD</code> 中分别定义 <code>SubFooB</code> 和 <code>SubFooC</code> 类型的变量来明确指定调用的是谁的 <code>Foo1()</code> 方法。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">package</span> main</div><div class="line"></div><div class="line"><span class="keyword">import</span> <span class="string">"../foo"</span></div><div class="line"></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</div><div class="line">sf := foo.NewSubFooB()</div><div class="line">sf.Foo1() <span class="comment">// Foo1() here</span></div><div class="line">sf.Foo2() <span class="comment">// <- Foo2() of B here <- 类似继承 + 方法重写</span></div><div class="line">sf.Foo3() <span class="comment">// Foo3() of B here</span></div><div class="line"></div><div class="line"> mf := foo.NewMiniFooD()</div><div class="line"><span class="comment">// mf.Foo1() // ambiguous selector mf.Foo1</span></div><div class="line">mf.Foo2()</div><div class="line">mf.Foo3()</div><div class="line">mf.Foo4() <span class="comment">// Foo4() of C here</span></div><div class="line">}</div></pre></td></tr></table></figure><h3 id="3-多态"><a href="#3-多态" class="headerlink" title="3.多态"></a>3.多态</h3><p>多态是面向对象编程的最后一个特性,通常是通过子类替换父类、在实际的代码运行过程中调用子类的方法来实现的。编程语言同样提供了不同的语法机制来支持,常见的有继承 + 方法重写、接口类和 duck typing。</p><p>实际上,对于继承 + 方法重写,我们在上面的例子中已经遇见过了,即 <code>sf.Foo2()</code> 这一句调用,<code>SubFooB</code> 重写了 <code>Foo</code> 的 <code>Foo2()</code> 方法,这里不再赘述。</p><p>在 Go 中,除了 struct,最常见的莫过于 interface 了。对于一个 interface,任何实现了该 interface 声明全部方法的 struct 的对象,都可以当做该 interface 类型的变量使用。</p><p>来看一个 Go 里很常见的 interface:<code>io.Writer</code>。</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">type Writer<span class="built_in"> interface </span>{</div><div class="line">Write(p []byte) (n int, err error)</div><div class="line">}</div></pre></td></tr></table></figure><p>这个接口只定义了一个 <code>Write</code> 函数,它可以将 <code>len(p)</code> 个字节从 <code>p</code> 写入数据流中,并返回写入的字节数和失败的错误信息。</p><p>我们接下来看它的一个使用场景:<code>func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {}</code> 函数。</p><p>从函数定义中可以看到,它接收一个 <code>io.Writer</code> 类型的参数作为入参。具体使用如下:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">package</span> main</div><div class="line"></div><div class="line"><span class="keyword">import</span> (</div><div class="line"><span class="string">"bufio"</span></div><div class="line"><span class="string">"fmt"</span></div><div class="line"><span class="string">"os"</span></div><div class="line">)</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</div><div class="line"><span class="comment">/*** polymorphic cases ***/</span></div><div class="line"></div><div class="line"> <span class="comment">// 1.os.Stdout as io.Writer</span></div><div class="line"><span class="keyword">const</span> name, age = <span class="string">"Kim"</span>, <span class="number">22</span></div><div class="line">n, err := fmt.Fprintf(os.Stdout, <span class="string">"%s is %d years old.\n"</span>, name, age)</div><div class="line"></div><div class="line"><span class="comment">// The n and err return values from Fprintf are</span></div><div class="line"><span class="comment">// those returned by the underlying io.Writer.</span></div><div class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> {</div><div class="line">fmt.Fprintf(os.Stderr, <span class="string">"Fprintf: %v\n"</span>, err)</div><div class="line">}</div><div class="line">fmt.Printf(<span class="string">"%d bytes written.\n"</span>, n)</div><div class="line"></div><div class="line"> <span class="comment">// 2.bufio.Writer as io.Writer</span></div><div class="line">w := bufio.NewWriter(os.Stdout)</div><div class="line">fmt.Fprint(w, <span class="string">"Hello, "</span>)</div><div class="line">fmt.Fprint(w, <span class="string">"world!"</span>)</div><div class="line">w.Flush() <span class="comment">// Don't forget to flush!</span></div><div class="line">}</div></pre></td></tr></table></figure><p>可以看到,我们既可以传入类型为 <code>os.File</code> 的 <code>os.Stdout</code>,也可以传入类型为 <code>bufio.Writer</code> 的 <code>w</code>,原因就是这两者都实现了接口定义的 <code>Write</code> 方法。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// os/file.go</span></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(f *File)</span> <span class="title">Write</span><span class="params">(b []<span class="keyword">byte</span>)</span> <span class="params">(n <span class="keyword">int</span>, err error)</span></span> {</div><div class="line">......</div><div class="line"><span class="keyword">return</span> n, err</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// bufio/bufio.go</span></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(b *Writer)</span> <span class="title">Write</span><span class="params">(p []<span class="keyword">byte</span>)</span> <span class="params">(nn <span class="keyword">int</span>, err error)</span></span> {</div><div class="line">......</div><div class="line"><span class="keyword">return</span> nn, <span class="literal">nil</span></div><div class="line">}</div></pre></td></tr></table></figure><p>由此就实现了 duck typing:只要看起来像鸭子、叫起来像鸭子、走路也像鸭子,就是个鸭子。同样,只要 struct 实现了 interface 中定义的全部方法,就可以作为这个 interface 类型来使用,并不需要其他编程语言中类似“implements”的声明。这也就 Go 中多态的实现方式。</p><p><strong>这里需要注意的一点是</strong>,具体类型实现方法的定义必须和 interface 中的函数定义的<strong>完全一致</strong>,包括函数名、参数类型和返回值类型。</p><p>比如这个例子:<br><figure class="highlight routeros"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">type Equaler<span class="built_in"> interface </span>{</div><div class="line"> Equal(Equaler) bool</div><div class="line">}</div></pre></td></tr></table></figure></p><p>这里定义了一个用来比较自身和另一个值是否相等的 interface,并且定义了一个类型 <code>T</code>:<br><figure class="highlight excel"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><span class="built_in">type</span> <span class="built_in">T</span> <span class="built_in">int</span></div><div class="line">func (<span class="built_in">t</span> <span class="built_in">T</span>) Equal(u <span class="built_in">T</span>) bool { return <span class="built_in">t</span> == u } // does <span class="built_in">not</span> satisfy Equaler</div></pre></td></tr></table></figure></p><p>上面的写法并不能算是实现了 <code>Equaler</code>,<code>T.Equal</code> 的参数类型是 <code>T</code>,并不是 interface 中定义的 <code>Equaler</code>。Go 并不会像其他编程语言一样自动做类型转换,需要开发者手动去转换,如下:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">type</span> T2 <span class="keyword">int</span></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(t T2)</span> <span class="title">Equal</span><span class="params">(u Equaler)</span> <span class="title">bool</span></span> { <span class="keyword">return</span> t == u.(T2) } <span class="comment">// satisfies Equaler</span></div></pre></td></tr></table></figure><p>这使得 Go 实现 interface 的规则十分简单:即函数的名字和签名是否和 interface 中定义的完全一致?</p><p>interface 使得 Go 的代码编写有了更好的扩展性和复用性,也使得我们可以更方便地实现面向对象“多态”这个特性,也使得很多设计模式、设计原则和编程技巧得以实现。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>每一种编程语言都有它的使命和魅力,如同每一种编程思想都有它的模式和章法。但我们都知道,面向对象编程是广泛使用的一种编程范式,但不是唯一的一种。当我们从日常的业务代码中抬起头来,追寻到可以快速改善编码质量的编码规范,寻觅到隐藏其中的代码重构的线索,遵循着经典的设计原则一步步前行,最终领悟到其中最根本的设计思想时,我们才能够说:“对于编写高质量的代码这件事,我心里有底了”。</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li><a href="https://golang.org/doc/faq" target="_blank" rel="external">https://golang.org/doc/faq</a></li><li><a href="https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem" target="_blank" rel="external">https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem</a></li><li><a href="https://code.tutsplus.com/tutorials/lets-go-object-oriented-programming-in-golang--cms-26540" target="_blank" rel="external">https://code.tutsplus.com/tutorials/lets-go-object-oriented-programming-in-golang–cms-26540</a></li></ul>]]></content>
<summary type="html">
<p>在我的印象中,正经而严肃认真地看“面向对象”这个概念,有这么几次:第一次,是大学时老师上课讲的,Car和Taxi,Animal和Dog、Cat,Teacher和Student,如此如此;第二次,是在臧成威老师的 iOS 黑魔法课中,学习如何用 C 语言实现面向对象的各种特性;第三次,应该就是最近,在学习设计模式中遇到最形而上之、又被最广泛使用的编程思想——面向对象。这一次,就从我写后端以来最常用的语言 Go 入手,研究一下它是如何体现面向对象思想、日常开发中又是如何使用 Go 进行面向对象编程的吧。</p>
<h2 id="使用-Go-如何实现面向对象编程?"><a href="#使用-Go-如何实现面向对象编程?" class="headerlink" title="使用 Go 如何实现面向对象编程?"></a>使用 Go 如何实现面向对象编程?</h2><p>首先,第一个问题是:“Go 是一门面向对象的语言吗?”</p>
</summary>
<category term="Golang" scheme="http://www.caliosd.gq/tags/Golang/"/>
</entry>
<entry>
<title>从 .proto 到 .pb.go</title>
<link href="http://www.caliosd.gq/2019/12/19/from-proto-to-go/"/>
<id>http://www.caliosd.gq/2019/12/19/from-proto-to-go/</id>
<published>2019-12-19T09:00:12.000Z</published>
<updated>2020-01-31T07:47:35.554Z</updated>
<content type="html"><![CDATA[<p>proto buffer在平时开发中使用很多,比如下面这个,就是一个最简单的 .proto 文件:<br><figure class="highlight protobuf"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line">syntax = <span class="string">"proto3"</span>;</div><div class="line"></div><div class="line"><span class="keyword">package</span> demopb;</div><div class="line"></div><div class="line"><span class="class"><span class="keyword">service</span> <span class="title">Demo</span> </span>{</div><div class="line"> <span class="function"><span class="keyword">rpc</span> Hello (HelloRequest) <span class="keyword">returns</span> (HelloResponse) {}</span></div><div class="line">}</div><div class="line"></div><div class="line">message HelloRequest {</div><div class="line"> string name = 1;</div><div class="line"> <span class="built_in">string</span> greeting = <span class="number">2</span>;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="class"><span class="keyword">message</span> <span class="title">HelloResponse</span> </span>{</div><div class="line"> <span class="built_in">string</span> name = <span class="number">1</span>;</div><div class="line"> <span class="built_in">string</span> greeting = <span class="number">2</span>;</div><div class="line">}</div></pre></td></tr></table></figure></p><p>那么它是如何从 .proto 转化成我们可以直接调用的 .go 文件呢?我们接下来依次拆解这几部分:</p><ol><li>package</li><li>message</li><li>field</li><li>service</li></ol><a id="more"></a><h2 id="1-package"><a href="#1-package" class="headerlink" title="1. package"></a>1. package</h2><ul><li>如果有 <code>package</code> 声明,会将声明的内容作为包名,其中 <code>.</code> 会被替换成 <code>_</code>;</li><li>如果没有 <code>package</code> 声明,会将文件名作为包名,同理,其中 <code>.</code> 会被替换成 <code>_</code>;</li></ul><figure class="highlight ada"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">package</span> <span class="title">dev.account (.proto) => <span class="keyword">package</span> dev_account (.go)</span></div></pre></td></tr></table></figure><ul><li>如果想覆盖掉默认生成的包名,可以增加 <code>go_package</code> 的设置;</li></ul><figure class="highlight ada"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">package</span> <span class="title">dev.account</span></div><div class="line">option go_package = <span class="string">"member"</span>; (.proto) => <span class="keyword">package</span> <span class="title">memeber (.go)</span></div></pre></td></tr></table></figure><h2 id="2-message"><a href="#2-message" class="headerlink" title="2. message"></a>2. message</h2><p>golang 并不会显式地声明一个 struct 实现了某个 interface,它用的是 “duck typing” 的方式。所以,只要 struct 实现了 interface 的所有方法,就可以把它当做 interface 用。</p><p>对于 proto 中的 <code>message</code> 也是这样,假设我们有一个简单的 <code>message</code>:</p><figure class="highlight protobuf"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">message</span> <span class="title">HelloRequest</span> </span>{}</div></pre></td></tr></table></figure><p>proto buffer的编译器会生成一个名为 <code>HelloRequest</code> 的 struct,同时为这个 struct 实现 <code>Message</code> 接口中的所有方法。</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">type Message<span class="built_in"> interface </span>{</div><div class="line"> Reset()</div><div class="line"> String() string</div><div class="line"> ProtoMessage()</div><div class="line">}</div></pre></td></tr></table></figure><p>生成的 struct:<br><figure class="highlight go"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">type</span> HelloRequest <span class="keyword">struct</span> {</div><div class="line">XXX_NoUnkeyedLiteral <span class="keyword">struct</span>{} <span class="string">`json:"-"`</span></div><div class="line">XXX_unrecognized []<span class="keyword">byte</span> <span class="string">`json:"-"`</span></div><div class="line">XXX_sizecache <span class="keyword">int32</span> <span class="string">`json:"-"`</span></div><div class="line">}</div><div class="line"><span class="comment">// 将proto的状态重置为默认值</span></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(m *HelloRequest)</span> <span class="title">Reset</span><span class="params">()</span></span> { *m = HelloRequest{} }</div><div class="line"><span class="comment">// proto的字符串展示</span></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(m *HelloRequest)</span> <span class="title">String</span><span class="params">()</span> <span class="title">string</span></span> { <span class="keyword">return</span> proto.CompactTextString(m) }</div><div class="line"><span class="comment">// 用做tag,保证其他人不会碰巧实现了 Message 接口</span></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(*HelloRequest)</span> <span class="title">ProtoMessage</span><span class="params">()</span></span> {}</div></pre></td></tr></table></figure></p><h3 id="2-1-预定义-message"><a href="#2-1-预定义-message" class="headerlink" title="2.1 预定义 message"></a>2.1 预定义 message</h3><p>protobuf 自带了一些预定义的 <code>message</code>,被称为“well-known types(WKTs)”。比如项目中经常会需要不包含任何字段的空结构体,就可以用 <code>Empty</code>,不需要自己再到处重复定义;再比如时间戳,也有相应的 message 可以直接拿来使用。</p><p>在 <code>$GOPATH/src/github.com/golang/protobuf/ptypes/empty/empty.proto</code> 中可以看到预定义的 <code>message Empty</code>,以及贴心的小例子:</p><figure class="highlight protobuf"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div></pre></td><td class="code"><pre><div class="line">syntax = <span class="string">"proto3"</span>;</div><div class="line"></div><div class="line"><span class="keyword">package</span> google.protobuf;</div><div class="line"></div><div class="line"><span class="keyword">option</span> csharp_namespace = <span class="string">"Google.Protobuf.WellKnownTypes"</span>;</div><div class="line"><span class="keyword">option</span> go_package = <span class="string">"github.com/golang/protobuf/ptypes/empty"</span>;</div><div class="line"><span class="keyword">option</span> java_package = <span class="string">"com.google.protobuf"</span>;</div><div class="line"><span class="keyword">option</span> java_outer_classname = <span class="string">"EmptyProto"</span>;</div><div class="line"><span class="keyword">option</span> java_multiple_files = <span class="literal">true</span>;</div><div class="line"><span class="keyword">option</span> objc_class_prefix = <span class="string">"GPB"</span>;</div><div class="line"><span class="keyword">option</span> cc_enable_arenas = <span class="literal">true</span>;</div><div class="line"></div><div class="line"><span class="comment">// A generic empty message that you can re-use to avoid defining duplicated</span></div><div class="line"><span class="comment">// empty messages in your APIs. A typical example is to use it as the request</span></div><div class="line"><span class="comment">// or the response type of an API method. For instance:</span></div><div class="line"><span class="comment">//</span></div><div class="line"><span class="comment">// service Foo {</span></div><div class="line"><span class="comment">// rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty);</span></div><div class="line"><span class="comment">// }</span></div><div class="line"><span class="comment">//</span></div><div class="line"><span class="comment">// The JSON representation for `Empty` is empty JSON object `{}`.</span></div><div class="line"><span class="class"><span class="keyword">message</span> <span class="title">Empty</span> </span>{}</div></pre></td></tr></table></figure><p>使用的时候,引入相应的 proto 即可。比如下面这个例子:</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line">import <span class="string">"google/protobuf/timestamp.proto"</span>;</div><div class="line">import <span class="string">"google/protobuf/empty.proto"</span>;</div><div class="line"><span class="built_in"></span></div><div class="line">service Demo {</div><div class="line"> rpc Hello (HelloRequest) returns (google.protobuf.Empty) {}</div><div class="line">}</div><div class="line"></div><div class="line">message HelloRequest {</div><div class="line"> string name = 1;</div><div class="line"> string greeting = 2;</div><div class="line"> google.protobuf.Timestamp last_modified = 3;</div><div class="line">}</div></pre></td></tr></table></figure><p>生成的 .pb.go 文件:</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div></pre></td><td class="code"><pre><div class="line">import (</div><div class="line"> empty <span class="string">"github.com/golang/protobuf/ptypes/empty"</span></div><div class="line">timestamp <span class="string">"github.com/golang/protobuf/ptypes/timestamp"</span></div><div class="line">)</div><div class="line"><span class="built_in">..</span>.<span class="built_in"></span></div><div class="line">type HelloRequest struct {</div><div class="line">Name string `protobuf:<span class="string">"bytes,1,opt,name=name,proto3"</span> json:<span class="string">"name,omitempty"</span>`</div><div class="line">Greeting string `protobuf:<span class="string">"bytes,2,opt,name=greeting,proto3"</span> json:<span class="string">"greeting,omitempty"</span>`</div><div class="line">LastModified *timestamp.Timestamp `protobuf:<span class="string">"bytes,3,opt,name=last_modified,json=lastModified,proto3"</span> json:<span class="string">"last_modified,omitempty"</span>`</div><div class="line">}</div><div class="line"><span class="built_in">..</span>.<span class="built_in"></span></div><div class="line">type DemoClient<span class="built_in"> interface </span>{</div><div class="line">Hello(ctx context.Context, <span class="keyword">in</span> *HelloRequest, opts <span class="built_in">..</span>.grpc.CallOption) (<span class="number">*e</span>mpty.Empty, error)</div><div class="line">}</div><div class="line"><span class="built_in">..</span>.<span class="built_in"></span></div><div class="line">type DemoServer<span class="built_in"> interface </span>{</div><div class="line">Hello(context.Context, *HelloRequest) (<span class="number">*e</span>mpty.Empty, error)</div><div class="line">}</div></pre></td></tr></table></figure><h2 id="3-field"><a href="#3-field" class="headerlink" title="3. field"></a>3. field</h2><p>field,即 message 中的某一个字段。常用的数据类型这里就不再赘述,有这样几种好玩儿的,在这里分享给大家。</p><h3 id="3-1-map"><a href="#3-1-map" class="headerlink" title="3.1 map"></a>3.1 map</h3><p>.proto:<br><figure class="highlight protobuf"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">message</span> <span class="title">Info</span> </span>{}</div><div class="line"><span class="class"><span class="keyword">message</span> <span class="title">RegisLoginResp</span> </span>{</div><div class="line"> ......</div><div class="line"> map<<span class="built_in">string</span>, Info> extra = <span class="number">4</span>;</div><div class="line">}</div></pre></td></tr></table></figure></p><p>生成的 .pb.go:<br><figure class="highlight elm"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">type</span> <span class="type">RegisLoginResp</span> struct {</div><div class="line">......</div><div class="line"><span class="type">Extra</span> map[string]*<span class="type">Info</span></div><div class="line">}</div></pre></td></tr></table></figure></p><h3 id="3-2-oneof"><a href="#3-2-oneof" class="headerlink" title="3.2 oneof"></a>3.2 oneof</h3><p>oneof 类型的 field 目前项目中比较少用到,但看起来很有用。<br>proto buffer的编译器会生成一个类型为 <code>isMessageName_MyField</code> 的接口类型的 field,同时为 oneof 中的每个 field 生成一个 struct,并实现 <code>isMessageName_MyField</code> 接口。<br>比如下面这个例子:</p><p>.proto:<br><figure class="highlight routeros"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line">message<span class="built_in"> Profile </span>{</div><div class="line"> oneof avatar {</div><div class="line"> string image_url = 1;</div><div class="line"> bytes image_data = 2;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></p><p>.pb.go:<br><figure class="highlight go"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">type</span> Profile <span class="keyword">struct</span> {</div><div class="line"><span class="comment">// Types that are valid to be assigned to Avatar:</span></div><div class="line"><span class="comment">//*Profile_ImageUrl</span></div><div class="line"><span class="comment">//*Profile_ImageData</span></div><div class="line">Avatar isProfile_Avatar <span class="string">`protobuf_oneof:"avatar"`</span></div><div class="line">}</div><div class="line"><span class="keyword">type</span> isProfile_Avatar <span class="keyword">interface</span> {</div><div class="line">isProfile_Avatar()</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">type</span> Profile_ImageUrl <span class="keyword">struct</span> {</div><div class="line">ImageUrl <span class="keyword">string</span></div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">type</span> Profile_ImageData <span class="keyword">struct</span> {</div><div class="line">ImageData []<span class="keyword">byte</span></div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(*Profile_ImageUrl)</span> <span class="title">isProfile_Avatar</span><span class="params">()</span></span> {}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(*Profile_ImageData)</span> <span class="title">isProfile_Avatar</span><span class="params">()</span></span> {}</div></pre></td></tr></table></figure></p><p>在真正使用的时候可以这样设置 field:<br><figure class="highlight dts"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line">p1 := <span class="variable">&account</span>.Profile{</div><div class="line"><span class="symbol"> Avatar:</span> <span class="variable">&account</span>.Profile_ImageUrl{<span class="string">"http://example.com/image.png"</span>},</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// imageData is []byte</span></div><div class="line">imageData := getImageData()</div><div class="line">p2 := <span class="variable">&account</span>.Profile{</div><div class="line"><span class="symbol"> Avatar:</span> <span class="variable">&account</span>.Profile_ImageData{imageData},</div><div class="line">}</div></pre></td></tr></table></figure></p><p>同样,在获取的时候可以用 type switch 来处理不同类型的 message:<br><figure class="highlight groovy"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">switch</span> <span class="string">x :</span>= m.Avatar.(type) {</div><div class="line"><span class="keyword">case</span> *account.<span class="string">Profile_ImageUrl:</span></div><div class="line"> <span class="comment">// Load profile image based on URL</span></div><div class="line"> <span class="comment">// using x.ImageUrl</span></div><div class="line"><span class="keyword">case</span> *account.<span class="string">Profile_ImageData:</span></div><div class="line"> <span class="comment">// Load profile image based on bytes</span></div><div class="line"> <span class="comment">// using x.ImageData</span></div><div class="line"><span class="keyword">case</span> <span class="string">nil:</span></div><div class="line"> <span class="comment">// The field is not set.</span></div><div class="line"><span class="string">default:</span></div><div class="line"> <span class="keyword">return</span> fmt.Errorf(<span class="string">"Profile.Avatar has unexpected type %T"</span>, x)</div><div class="line">}</div></pre></td></tr></table></figure></p><h3 id="3-3-“XXX-”-field"><a href="#3-3-“XXX-”-field" class="headerlink" title="3.3 “XXX_” field"></a>3.3 “XXX_” field</h3><p>在生成的.pb.go文件中,常会看到很多以 “XXX<em>” 为前缀的字段,它们主要用来存放未知的字段。比如当 proto 的客户端和服务端使用的 proto 版本不同时,proto 解码时可能在序列化的数据中有多余的字段,这些字段会被存放在 “XXX</em>” 中去,以备向后兼容。</p><p>在 protobuf 的 repo 下,有提出 <a href="https://github.com/golang/protobuf/issues/276" target="_blank" rel="external">issue</a> 要求不再生成 “XXX_” 的字段,可能会在下个版本中实现。<br>如果实在不喜欢这些多余字段,可以使用 <a href="https://github.com/gogo/protobuf" target="_blank" rel="external">gogo/protobuf</a> 来代替官方的实现。</p><h2 id="4-Service"><a href="#4-Service" class="headerlink" title="4. Service"></a>4. Service</h2><p>protobuf 的 go 的代码生成器默认并不会生成 service 相关的内容,只有指定了使用 gRPC 的插件,才会生成。如下,后者会生成 client 和 server 端的 stub,前者并不会。</p><figure class="highlight haml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">$ protoc -I/usr/local/include -I. \</div><div class="line"> -<span class="ruby">I$(GOPATH)/src/ \</span></div><div class="line"> -<span class="ruby">-go_out=. demo.proto</span></div></pre></td></tr></table></figure><p>vs<br><figure class="highlight haml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">$ protoc -I/usr/local/include -I. \</div><div class="line"> -<span class="ruby">I$(GOPATH)/src/ \</span></div><div class="line"> -<span class="ruby">-go_out=plugins=grpc,paths=<span class="symbol">source_relative:</span>. demo.proto</span></div></pre></td></tr></table></figure></p><blockquote><p>注:这里补充一下 <code>protoc</code> 的用法:</p><ul><li><code>-IPath,--proto_path=PATH</code>:指定搜索import文件的路径,按照给定的顺序查找,如果没有指定,会默认搜索当前工作目录。</li><li><code>--xxx_out</code>:生成相应语言文件的路径。</li></ul></blockquote><p>gRPC 允许我们定义四种类型的 service:</p><ol><li>Unary</li><li>Server streaming</li><li>Client streaming </li><li>Bidirectional streaming </li></ol><h3 id="4-1-Unary-service"><a href="#4-1-Unary-service" class="headerlink" title="4.1 Unary service"></a>4.1 Unary service</h3><p>客户端发送请求,服务端返回响应,最常用的 service 类型,上面已经举过例子了,这里不再赘述。</p><h3 id="4-2-Server-streaming-service"><a href="#4-2-Server-streaming-service" class="headerlink" title="4.2 Server streaming service"></a>4.2 Server streaming service</h3><p>客户端发送请求给服务端,并得到一个可以读到一系列 message 的数据流,客户端可以从流中读取数据直到再无数据。同时,gRPC可以保证在一次rpc请求中数据的有序性。</p><p>比如,在上面的service中,增加一个 <code>HelloServerStream</code>:</p><figure class="highlight protobuf"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">service</span> <span class="title">Demo</span> </span>{</div><div class="line"> ......</div><div class="line"> <span class="function"><span class="keyword">rpc</span> HelloServerStream (HelloRequest) <span class="keyword">returns</span> (stream HelloResponse) {}</span></div><div class="line">}</div></pre></td></tr></table></figure><p>会发现,生成的 .pb.go 中除了在 DemoClient 和 DemoServer 的 stub 中增加了对应方法之外,还增加了 streamClient 和 streamServer 接口,用来提供发送和接收数据的方法。<br><figure class="highlight routeros"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div></pre></td><td class="code"><pre><div class="line">type DemoClient<span class="built_in"> interface </span>{</div><div class="line">Hello(ctx context.Context, <span class="keyword">in</span> *HelloRequest, opts <span class="built_in">..</span>.grpc.CallOption) (<span class="number">*e</span>mpty.Empty, error)</div><div class="line">HelloServerStream(ctx context.Context, <span class="keyword">in</span> *HelloRequest, opts <span class="built_in">..</span>.grpc.CallOption) (Demo_HelloServerStreamClient, error) <-- 新增</div><div class="line">}</div><div class="line"><span class="built_in"></span></div><div class="line">type DemoServer<span class="built_in"> interface </span>{</div><div class="line">Hello(context.Context, *HelloRequest) (<span class="number">*e</span>mpty.Empty, error)</div><div class="line">HelloServerStream(*HelloRequest, Demo_HelloServerStreamServer) <span class="builtin-name">error</span></div><div class="line">}</div><div class="line"><span class="built_in"></span></div><div class="line">type Demo_HelloServerStreamClient<span class="built_in"> interface </span>{</div><div class="line">Recv() (*HelloResponse, error)</div><div class="line">grpc.ClientStream</div><div class="line">}</div><div class="line"><span class="built_in"></span></div><div class="line">type Demo_HelloServerStreamServer<span class="built_in"> interface </span>{</div><div class="line">Send(*HelloResponse) <span class="builtin-name">error</span></div><div class="line">grpc.ServerStream</div><div class="line">}</div></pre></td></tr></table></figure></p><p>在使用时,客户端可以像本地方法一样调用:<br><figure class="highlight stata"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// client.go</span></div><div class="line">func serverStream(client demopb.DemoClient, req *demopb.HelloRequest) {</div><div class="line"><span class="keyword">log</span>.Infof(<span class="string">"serverStream req: %v"</span>, req)</div><div class="line">ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)</div><div class="line">defer cancel()</div><div class="line">stream, <span class="keyword">err</span> := client.HelloServerStream(ctx, req)</div><div class="line"><span class="keyword">if</span> <span class="keyword">err</span> != nil {</div><div class="line"><span class="keyword">log</span>.Fatalf(<span class="string">"%v.HelloServerStream(_) = _, %v"</span>, client, <span class="keyword">err</span>)</div><div class="line">}</div><div class="line"><span class="keyword">for</span> {</div><div class="line">greeting, <span class="keyword">err</span> := stream.Recv() <span class="comment">// 不断从stream中读取数据</span></div><div class="line"><span class="keyword">if</span> <span class="keyword">err</span> == io.EOF { <span class="comment">// 直到读完数据,结束</span></div><div class="line"><span class="keyword">break</span></div><div class="line">}</div><div class="line"><span class="keyword">if</span> <span class="keyword">err</span> != nil {</div><div class="line"><span class="keyword">log</span>.Fatalf(<span class="string">"recv from %v.HelloServerStream(_) = _, %v"</span>, client, <span class="keyword">err</span>)</div><div class="line">}</div><div class="line"><span class="keyword">log</span>.Infof(<span class="string">"serverStream resp: %v"</span>, greeting)</div><div class="line">}</div><div class="line">}</div></pre></td></tr></table></figure></p><p>而服务端负责不断发送数据:<br><figure class="highlight go"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// server.go</span></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(h *Handler)</span> <span class="title">HelloServerStream</span><span class="params">(req *demopb.HelloRequest, stream demopb.Demo_HelloServerStreamServer)</span> <span class="title">error</span></span> {</div><div class="line"><span class="keyword">if</span> req.Name == <span class="string">"xxx"</span> {</div><div class="line"><span class="keyword">return</span> errno.UserNotFound</div><div class="line">}</div><div class="line">greetings := []*demopb.HelloResponse{</div><div class="line">&demopb.HelloResponse{</div><div class="line">Name: <span class="string">"alice"</span>,</div><div class="line">Greeting: <span class="string">"morning~"</span>,</div><div class="line">},</div><div class="line">&demopb.HelloResponse{</div><div class="line">Name: <span class="string">"bob"</span>,</div><div class="line">Greeting: <span class="string">"lovely day~"</span>,</div><div class="line">},</div><div class="line">}</div><div class="line"><span class="keyword">for</span> _, greeting := <span class="keyword">range</span> greetings {</div><div class="line"><span class="keyword">if</span> err := stream.Send(greeting); err != <span class="literal">nil</span> { <span class="comment">// 不断将数据发出去</span></div><div class="line">log.Errorf(<span class="string">"HelloServerStream: send err: %+v, req: %+v "</span>, err, req)</div><div class="line"><span class="keyword">return</span> err</div><div class="line">}</div><div class="line">}</div><div class="line"><span class="keyword">return</span> <span class="literal">nil</span></div><div class="line">}</div></pre></td></tr></table></figure></p><h3 id="4-2-Client-streaming-service"><a href="#4-2-Client-streaming-service" class="headerlink" title="4.2 Client streaming service"></a>4.2 Client streaming service</h3><p>反过来,客户端向服务端上传一组流数据,服务端依次读取。<br><figure class="highlight go"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// client.go</span></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">clientStream</span><span class="params">(client demopb.DemoClient)</span></span> {</div><div class="line">log.Info(<span class="string">"clientStream req:"</span>)</div><div class="line">ctx, cancel := context.WithTimeout(context.Background(), <span class="number">10</span>*time.Second)</div><div class="line"><span class="keyword">defer</span> cancel()</div><div class="line">stream, err := client.HelloClientStream(ctx) <span class="comment">// 得到可以发送流数据的stream</span></div><div class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> {</div><div class="line">log.Fatalf(<span class="string">"%v.HelloClientStream(_) = _, %v"</span>, client, err)</div><div class="line">}</div><div class="line"></div><div class="line">greetings := []*demopb.HelloRequest{</div><div class="line">&demopb.HelloRequest{</div><div class="line">Name: <span class="string">"chris"</span>,</div><div class="line">Greeting: <span class="string">"night~"</span>,</div><div class="line">},</div><div class="line">&demopb.HelloRequest{</div><div class="line">Name: <span class="string">"dar"</span>,</div><div class="line">Greeting: <span class="string">"sweet dream~"</span>,</div><div class="line">},</div><div class="line">}</div><div class="line"><span class="keyword">for</span> _, greeting := <span class="keyword">range</span> greetings {</div><div class="line"><span class="keyword">if</span> err := stream.Send(greeting); err != <span class="literal">nil</span> { <span class="comment">// 发送</span></div><div class="line">log.Fatalf(<span class="string">"%v.Send(%v) = %v"</span>, stream, greeting, err)</div><div class="line">}</div><div class="line">}</div><div class="line">reply, err := stream.CloseAndRecv() <span class="comment">// 接收到结束传输的最后一条数据,“End,bye~!”</span></div><div class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> {</div><div class="line">log.Fatalf(<span class="string">"%v.CloseAndRecv() got error %v, want %v"</span>, stream, err, <span class="literal">nil</span>)</div><div class="line">}</div><div class="line">log.Infof(<span class="string">"clientStream summary: %v"</span>, reply)</div><div class="line">}</div></pre></td></tr></table></figure></p><figure class="highlight go"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// server.go</span></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(h *Handler)</span> <span class="title">HelloClientStream</span><span class="params">(stream demopb.Demo_HelloClientStreamServer)</span> <span class="title">error</span></span> {</div><div class="line"><span class="keyword">for</span> {</div><div class="line">greeting, err := stream.Recv() <span class="comment">// 依次接收</span></div><div class="line"><span class="keyword">if</span> err == io.EOF { <span class="comment">// 直到数据末尾</span></div><div class="line"><span class="keyword">return</span> stream.SendAndClose(&demopb.HelloResponse{</div><div class="line">Name: <span class="string">"End"</span>,</div><div class="line">Greeting: <span class="string">"bye~!"</span>,</div><div class="line">})</div><div class="line">}</div><div class="line">log.Infof(<span class="string">"HelloClientStream: recv: %+v"</span>, greeting)</div><div class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> {</div><div class="line"><span class="keyword">return</span> err</div><div class="line">}</div><div class="line">}</div><div class="line">}</div></pre></td></tr></table></figure><h3 id="4-4-Bidirectional-streaming-service"><a href="#4-4-Bidirectional-streaming-service" class="headerlink" title="4.4 Bidirectional streaming service"></a>4.4 Bidirectional streaming service</h3><p>顾名思义,客户端和服务端发送的都是流数据,服务端接收流数据的同时,也返回给客户端流数据。</p><h2 id="5-总结"><a href="#5-总结" class="headerlink" title="5. 总结"></a>5. 总结</h2><p>上面我们提到的,只是使用 gRPC 中的一小段过程,即:从 .proto 定义生成我们所需要的 go 语言实现。<br>当我们将这一部分 zoom out,去观察数据如何在客户端按照 IDL 进行编码和压缩,而后又如何传输到客户端,最终又如何由客户端依照 IDL 反向解码成原始数据,就会发现,上面提到的一切,实在只是冰山一角。<br>而这完整的流程,将会是另一篇文章的主线(又在给自己挖坑┓( ´∀` )┏)。希望我能尽快将新的一篇展现给你,一起走另一段奇妙的旅程。</p><p><br></p><p>Calios<br>2019.12.21</p>]]></content>
<summary type="html">
<p>proto buffer在平时开发中使用很多,比如下面这个,就是一个最简单的 .proto 文件:<br><figure class="highlight protobuf"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line">syntax = <span class="string">"proto3"</span>;</div><div class="line"></div><div class="line"><span class="keyword">package</span> demopb;</div><div class="line"></div><div class="line"><span class="class"><span class="keyword">service</span> <span class="title">Demo</span> </span>&#123;</div><div class="line"> <span class="function"><span class="keyword">rpc</span> Hello (HelloRequest) <span class="keyword">returns</span> (HelloResponse) &#123;&#125;</span></div><div class="line">&#125;</div><div class="line"></div><div class="line">message HelloRequest &#123;</div><div class="line"> string name = 1;</div><div class="line"> <span class="built_in">string</span> greeting = <span class="number">2</span>;</div><div class="line">&#125;</div><div class="line"></div><div class="line"><span class="class"><span class="keyword">message</span> <span class="title">HelloResponse</span> </span>&#123;</div><div class="line"> <span class="built_in">string</span> name = <span class="number">1</span>;</div><div class="line"> <span class="built_in">string</span> greeting = <span class="number">2</span>;</div><div class="line">&#125;</div></pre></td></tr></table></figure></p>
<p>那么它是如何从 .proto 转化成我们可以直接调用的 .go 文件呢?我们接下来依次拆解这几部分:</p>
<ol>
<li>package</li>
<li>message</li>
<li>field</li>
<li>service</li>
</ol>
</summary>
<category term="Golang" scheme="http://www.caliosd.gq/tags/Golang/"/>
<category term="protobuf" scheme="http://www.caliosd.gq/tags/protobuf/"/>
</entry>
<entry>
<title>WWDC 2017-412: Auto Layout Techniques in Interface Builder</title>
<link href="http://www.caliosd.gq/2017/10/26/WWDC-2017-412-Auto-Layout-Techniques-in-IB/"/>
<id>http://www.caliosd.gq/2017/10/26/WWDC-2017-412-Auto-Layout-Techniques-in-IB/</id>
<published>2017-10-26T13:26:40.000Z</published>
<updated>2019-12-17T15:30:59.732Z</updated>
<content type="html"><![CDATA[<p>Here’s a summary for WWDC 2017, Session 412: Auto Layout Techniques in Interface Builder<br><a href="https://developer.apple.com/videos/play/wwdc2017/412/" target="_blank" rel="external">https://developer.apple.com/videos/play/wwdc2017/412/</a></p><a id="more"></a><h3 id="Changing-layout-at-runtime"><a href="#Changing-layout-at-runtime" class="headerlink" title="Changing layout at runtime"></a>Changing layout at runtime</h3><ul><li>Wrap the view containing its subviews and constraints all set, then set the height of this super view to 0, it gets the affects of hiding the elements;</li><li>When <code>xxxConstraint.isActive = false/true</code> come in pairs, set <code>isActive = false</code> first to avoid the warning message in console;</li><li>To add some animation to this duration, use a UIView animation block to wrap layoutIfNeeded() as following:</li></ul><figure class="highlight less"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="selector-tag">UIView</span><span class="selector-class">.animate</span>(<span class="attribute">withDuration</span>: <span class="number">0.25</span>) {</div><div class="line"> <span class="selector-tag">self</span><span class="selector-class">.view</span><span class="selector-class">.layoutIfNeeded</span>()</div><div class="line">}</div></pre></td></tr></table></figure><h3 id="Tracking-touch"><a href="#Tracking-touch" class="headerlink" title="Tracking touch"></a>Tracking touch</h3><ul><li>View position is a result of multiple properties:<ul><li><code>frame</code>: derived from constraints;</li><li><code>transform</code>;</li></ul></li><li>Transform is great for temporary changes:<ul><li><code>transform</code> property offsets from frame;</li><li>❓<code>CGAffineTransform = translation + rotation + scale</code>; (<strong>Explore later</strong>)</li><li>Reset to <code>CGAffineTransform.identity</code> when done.</li></ul></li></ul><h3 id="Dynamic-type"><a href="#Dynamic-type" class="headerlink" title="Dynamic type"></a>Dynamic type</h3><ul><li>Fantastic method to debug dynamic type: <em>Xcode -> Menu -> Accessibility Inspector -> Setting -> Drag&Drop Font Size</em>;</li><li>Check <em>vertical baseline standard spacing</em> for dynamic type.</li></ul><h3 id="Safe-Areas-Layout-Guide"><a href="#Safe-Areas-Layout-Guide" class="headerlink" title="Safe Areas Layout Guide"></a>Safe Areas Layout Guide</h3><ul><li>A new property in UIView;</li><li>Available in tvOS;</li><li>Title safe, unobscured content;</li><li>iOS storyboards:<ul><li>Constraints automatically upgrade;</li><li>Backwards deployable;</li></ul></li><li>LargeTitle support since iOS 11.0.</li></ul><figure class="highlight jboss-cli"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">if</span> <span class="comment">#available(iOS 11.0, *) {</span></div><div class="line"> navigationController?<span class="string">.navigationBar.prefersLargeTitles</span> = <span class="literal">true</span></div><div class="line"> vc.navigationItem.largeTitleDisplayMode = <span class="string">.always</span></div><div class="line">}</div></pre></td></tr></table></figure><h3 id="Portional-positioning"><a href="#Portional-positioning" class="headerlink" title="Portional positioning"></a>Portional positioning</h3><ul><li>Using spacer view is an nice option when needed in Interface Builder;<ul><li>Mark as hidden;</li></ul></li><li>Use UILayoutGuide programmatically.</li></ul><h3 id="Stack-view-adaptive-layout"><a href="#Stack-view-adaptive-layout" class="headerlink" title="Stack view adaptive layout"></a>Stack view adaptive layout</h3><ul><li>From Xcode 9, hidden property can vary by size class.</li></ul>]]></content>
<summary type="html">
<p>Here’s a summary for WWDC 2017, Session 412: Auto Layout Techniques in Interface Builder<br><a href="https://developer.apple.com/videos/play/wwdc2017/412/" target="_blank" rel="external">https://developer.apple.com/videos/play/wwdc2017/412/</a></p>
</summary>
<category term="iOS" scheme="http://www.caliosd.gq/tags/iOS/"/>
</entry>
<entry>
<title>iOS的内存管理之ARC部分总结</title>
<link href="http://www.caliosd.gq/2017/06/29/memory-management-with-ARC/"/>
<id>http://www.caliosd.gq/2017/06/29/memory-management-with-ARC/</id>
<published>2017-06-29T01:46:40.000Z</published>
<updated>2018-09-15T08:14:44.000Z</updated>
<content type="html"><![CDATA[<p>在《Objective-C高级编程:iOS与OS X多线程和内存管理》一书中,对于ARC的部分有详尽的讲解。我将这一部分整理成思维导图,算是一个归档吧。</p><a id="more"></a><p><img src="http://7xkwcv.com1.z0.glb.clouddn.com/iOS%E7%9A%84%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86.png" alt="内存管理之ARC部分"></p><p>如下是一些需要注意的点:</p><h3 id="0-id"><a href="#0-id" class="headerlink" title="0.id"></a>0.<code>id</code></h3><ul><li><code>id</code>是OC中对象标识的数据类型,可以用来指定任何类。<code>id</code>是所有对象的终极超类。</li><li><code><objc/objc.h></code>中<code>id</code>的定义:</li></ul><figure class="highlight smali"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">/// Represents an<span class="built_in"> instance </span>of a class.</div><div class="line">struct objc_object {</div><div class="line"> Class isa OBJC_ISA_AVAI<span class="class">LABILITY;</span></div><div class="line">};</div><div class="line"></div><div class="line">/// A pointer to an<span class="built_in"> instance </span>of a class.</div><div class="line">typedef struct objc_object *id;</div></pre></td></tr></table></figure><ul><li>虽然看起来是指向结构体的指针,但对于OC的编译器而言,<code>id</code>使得原本的静态类型的语言变得动态起来。这就意味着,它指向的对象的类型是不被编译器检查的。</li><li>ARC有效时,<code>id</code>类型和对象类型同C语言其他类型不同,其类型上必须附加所有权修饰符,包括:<code>__strong</code>,<code>__weak</code>,<code>__unsafe_unretain</code>和<code>__autoreleasing</code>修饰符。</li><li><code>__strong</code>,<code>__weak</code>和<code>__autoreleasing</code>修饰符一起,可以保证将附有这些修饰符的自动变量初始化为<code>nil</code>。</li></ul><h3 id="1-引用计数规则"><a href="#1-引用计数规则" class="headerlink" title="1.引用计数规则"></a>1.引用计数规则</h3><ul><li>谁使用谁释放,谁创建谁释放。(谁污染谁治理 =。=)</li></ul><h3 id="2-是在预编译-编译-链接-运行-哪个阶段进行?"><a href="#2-是在预编译-编译-链接-运行-哪个阶段进行?" class="headerlink" title="2.是在预编译/编译/链接/运行 哪个阶段进行?"></a>2.是在预编译/编译/链接/运行 哪个阶段进行?</h3><ul><li>所有的retain、release、strong属性的会在编译时处理。</li><li>weak属性会在运行时处理。</li></ul><h3 id="3-strong、-weak、-unsafe-unretain"><a href="#3-strong、-weak、-unsafe-unretain" class="headerlink" title="3.__strong、__weak、__unsafe_unretain"></a>3.<code>__strong</code>、<code>__weak</code>、<code>__unsafe_unretain</code></h3><ul><li><code>__strong</code>:是<code>id</code>类型和<strong>对象类型</strong>默认的所有权修饰符。附有<code>__strong</code>修饰符的变量,在超出其变量作用域时,即在该变量被废弃时,会释放其被赋予的对象。表示逻辑上强持有一个对象。规则是:指针被赋值时同时进行retain处理;指针销毁时对指针的内容进行release。</li><li><code>__weak</code>:指针被赋值时,不会对所赋的对象进行retain,意味着引用计数会随时为零,一旦为零,就会被置为<code>nil</code>,也就是说,要再引用计数为零之前置为<code>nil</code>。</li><li><code>__unsafe_unretain</code>:不安全的不持有。规则是:声明一个指针,对赋值的对象不进行retain,如果对象为零了,那么指针会继续指向这个对象。</li><li><code>__weak</code>会有性能损耗,所以在处理极大对象的时候会使用<code>__unsafe_unretain</code>属性。</li></ul><h3 id="4-strong,weak,assign"><a href="#4-strong,weak,assign" class="headerlink" title="4.strong,weak,assign"></a>4.<code>strong</code>,<code>weak</code>,<code>assign</code></h3><ul><li><code>strong</code>:和<code>__strong</code>是一样的,在dealloc的时候release。</li><li><code>weak</code>:和<code>__weak</code>是一样的,一旦引用计数变为零,会自动置为<code>nil</code>。</li><li><code>assign</code>:能够声明很多标量(float,int等),用它来声明变量时,和<code>__unsafe_unretain</code>一样,声明一个指针的时候会对它进行持有,当为零的时候也不会自动释放。</li></ul><h3 id="5-weak详解"><a href="#5-weak详解" class="headerlink" title="5.weak详解"></a>5.<code>weak</code>详解</h3><ul><li>工作在运行时:编译时是按照逻辑编译单元(一个.m文件)进行编译的,在一个编译单元中无法知道其他编译单元的情况,无法判断是否为零、是否需要置为<code>nil</code>。</li><li>如何置为<code>nil</code>:底层有一个哈希表,哈希表中存储所有weak指向的对象。用weak指针指向的值(对象的地址,是一个栈/堆上的变量)作为key,所有指向它的地址作为value。每当对象dealloc时,就会检查这个表,同时把表中所有指向这个对象的指针都置为nil。</li></ul><h3 id="6-归零弱引用(Zeroing-weak-reference)"><a href="#6-归零弱引用(Zeroing-weak-reference)" class="headerlink" title="6.归零弱引用(Zeroing weak reference)"></a>6.归零弱引用(Zeroing weak reference)</h3><p>即让对象自己去清空弱引用的对象,这种特殊的弱引用被称为归零弱引用。在它指向的对象释放后,这些弱引用会被设置为零(即nil)。</p><p>有两种方式可以声明归零弱引用:</p><ul><li>声明变量时使用<code>__weak</code>关键字或对属性使用weak特性。</li><li>在不支持弱引用的旧系统上,可以使用苹果公司提供的<code>__unsafe_unretained</code>关键字和<code>unsafe_unretained</code>特性。</li></ul><h3 id="7-循环引用"><a href="#7-循环引用" class="headerlink" title="7.循环引用"></a>7.循环引用</h3><ul><li><code>a.propertyB = b; b.propertyA = a;</code> b的引用计数+1,a的引用计数+1。引用计数一直无法为零,造成循环引用。</li><li>如何解?<ul><li>原因在于,b和a都会引用计数至少为1。</li><li>暴力解法:b或者a多调用一下release。然后就挂了。ㄟ( ▔. ▔ )ㄏ</li><li>简单解法:任何一端置为nil。</li></ul></li></ul><h3 id="Ref"><a href="#Ref" class="headerlink" title="Ref"></a>Ref</h3><ul><li>《Objective-C高级编程:iOS与OS X多线程和内存管理》</li><li>臧成威《现代OC内存管理》:<a href="http://www.stuq.org/course/1044/courseware/1594" target="_blank" rel="external">http://www.stuq.org/course/1044/courseware/1594</a></li></ul>]]></content>
<summary type="html">
<p>在《Objective-C高级编程:iOS与OS X多线程和内存管理》一书中,对于ARC的部分有详尽的讲解。我将这一部分整理成思维导图,算是一个归档吧。</p>
</summary>
<category term="iOS" scheme="http://www.caliosd.gq/tags/iOS/"/>
</entry>
<entry>
<title>关于copy的常见问题</title>
<link href="http://www.caliosd.gq/2017/06/28/copy/"/>
<id>http://www.caliosd.gq/2017/06/28/copy/</id>
<published>2017-06-28T06:22:49.000Z</published>
<updated>2018-09-15T08:19:24.000Z</updated>
<content type="html"><![CDATA[<h3 id="1-property中的copy关键字如何使用?"><a href="#1-property中的copy关键字如何使用?" class="headerlink" title="1.@property中的copy关键字如何使用?"></a>1.<code>@property</code>中的<code>copy</code>关键字如何使用?</h3><ul><li>对于<code>NSString</code>、<code>NSArray</code>、<code>NSDictionary</code>而言:见下面几个问题的回答。</li><li>对于block而言:需要使用<code>copy</code>是为了在原有上下文范围外,继续追踪它捕获的状态。在使用ARC的时候不需要担心这个问题,因为是自动进行的,但是最佳实践是为property属性标记上这个必然的行为(即,添加上copy关键字)。</li></ul><a id="more"></a><p><img src="http://7xkwcv.com1.z0.glb.clouddn.com/block%20copy.png" alt="block copy"></p><p>其他关于ARC遇上Block的问题,可以参见之后的<a href="someother link">iOS的内存管理之block</a>一文。</p><h3 id="2-用-property声明的NSString-NSArray-NSDictionary,常用copy关键字,为什么?如果改用strong,可能会有什么问题?"><a href="#2-用-property声明的NSString-NSArray-NSDictionary,常用copy关键字,为什么?如果改用strong,可能会有什么问题?" class="headerlink" title="2.用@property声明的NSString/NSArray/NSDictionary,常用copy关键字,为什么?如果改用strong,可能会有什么问题?"></a>2.用<code>@property</code>声明的<code>NSString/NSArray/NSDictionary</code>,常用<code>copy</code>关键字,为什么?如果改用<code>strong</code>,可能会有什么问题?</h3><p><strong>结论:</strong>使用<code>copy</code>是为了防止传入的属性值被意外更改。如果改为<code>strong</code>,一旦传入的参数是一个可变类型的对象,那么这个参数就会随着这个对象的变化而变化,而这个变化并不一定是<code>@property</code>所属的类内部所期望的,造成不可控的结果。</p><p><strong>做个实验来验证:</strong><br><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// strong vs copy (string)</span></div><div class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) <span class="built_in">NSString</span> *strongString;</div><div class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">copy</span>) <span class="built_in">NSString</span> *copiedString;</div><div class="line"></div><div class="line"></div><div class="line"><span class="built_in">NSMutableString</span> *s = [<span class="built_in">NSMutableString</span> stringWithFormat:<span class="string">@"test"</span>];</div><div class="line"><span class="keyword">self</span>.strongString = s;</div><div class="line"><span class="keyword">self</span>.copiedString = s;</div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"original string: %p"</span>, s);</div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"strong string: %p"</span>, _strongString);</div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"copied string: %p"</span>, _copiedString);</div><div class="line">[s appendString:<span class="string">@" something else"</span>];</div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"original = %@, strong = %@, copied = %@"</span>, s, <span class="keyword">self</span>.strongString, <span class="keyword">self</span>.copiedString);</div><div class="line"></div><div class="line"><span class="comment">// Console:</span></div><div class="line"><span class="number">2017</span><span class="number">-06</span><span class="number">-29</span> <span class="number">11</span>:<span class="number">19</span>:<span class="number">16.453</span> iOS Example[<span class="number">68464</span>:<span class="number">37016982</span>] original string: <span class="number">0x618000263a40</span></div><div class="line"><span class="number">2017</span><span class="number">-06</span><span class="number">-29</span> <span class="number">11</span>:<span class="number">19</span>:<span class="number">16.453</span> iOS Example[<span class="number">68464</span>:<span class="number">37016982</span>] <span class="keyword">strong</span> string: <span class="number">0x618000263a40</span></div><div class="line"><span class="number">2017</span><span class="number">-06</span><span class="number">-29</span> <span class="number">11</span>:<span class="number">19</span>:<span class="number">16.453</span> iOS Example[<span class="number">68464</span>:<span class="number">37016982</span>] copied string: <span class="number">0xa000000747365744</span></div><div class="line"><span class="number">2017</span><span class="number">-06</span><span class="number">-29</span> <span class="number">11</span>:<span class="number">19</span>:<span class="number">16.453</span> iOS Example[<span class="number">68464</span>:<span class="number">37016982</span>] original = test something <span class="keyword">else</span>, <span class="keyword">strong</span> = test something <span class="keyword">else</span>, copied = test</div></pre></td></tr></table></figure></p><p>可以看到,原可变字符串的地址和strong字符串的地址相同,只进行了指针拷贝,而copy字符串的地址是另一个。因此在修改原字符串的时候,使用copy不会改变字符串,而使用strong则会随之变化。</p><p><img src="http://7xkwcv.com1.z0.glb.clouddn.com/copy&strong.png" alt="copy&strong"></p><h3 id="3-用-property声明的NSString、NSArray、NSDictionary的可变类型NSMutableString、NSMutableArray、NSMutableDictionary,用copy是否有问题?"><a href="#3-用-property声明的NSString、NSArray、NSDictionary的可变类型NSMutableString、NSMutableArray、NSMutableDictionary,用copy是否有问题?" class="headerlink" title="3.用@property声明的NSString、NSArray、NSDictionary的可变类型NSMutableString、NSMutableArray、NSMutableDictionary,用copy是否有问题?"></a>3.用<code>@property</code>声明的<code>NSString、NSArray、NSDictionary</code>的可变类型<code>NSMutableString、NSMutableArray、NSMutableDictionary</code>,用<code>copy</code>是否有问题?</h3><p><strong>结论:</strong>会有问题。因为<code>copy</code>只是将可变类型的对象复制成为不可变的对象,也就无法进行添加、删除、修改的操作。</p><p><strong>做个实验来验证:(顺便和<code>strong</code>进行对比)</strong></p><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// strong vs copy (array)</span></div><div class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) <span class="built_in">NSMutableArray</span> *strongMutableArray;</div><div class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">copy</span>) <span class="built_in">NSMutableArray</span> *copiedMutableArray;</div><div class="line"></div><div class="line"><span class="built_in">NSMutableArray</span> *a = [<span class="built_in">NSMutableArray</span> arrayWithObjects:@<span class="number">1</span>, @<span class="number">2</span>, <span class="literal">nil</span>];</div><div class="line"><span class="keyword">self</span>.strongMutableArray = a;</div><div class="line"><span class="keyword">self</span>.copiedMutableArray = a;</div><div class="line">[<span class="keyword">self</span>.strongMutableArray removeObjectAtIndex:<span class="number">0</span>];</div><div class="line">[<span class="keyword">self</span>.copiedMutableArray removeObjectAtIndex:<span class="number">0</span>];<span class="comment">// <-- Crash here!!</span></div><div class="line"></div><div class="line"><span class="comment">// Console:</span></div><div class="line"><span class="number">2017</span><span class="number">-06</span><span class="number">-29</span> <span class="number">11</span>:<span class="number">19</span>:<span class="number">16.453</span> iOS Example[<span class="number">68464</span>:<span class="number">37016982</span>] -[__NSArrayI removeObjectAtIndex:]: unrecognized selector sent to instance <span class="number">0x60800003f3c0</span></div><div class="line">(lldb) po <span class="keyword">self</span>.copiedMutableArray</div><div class="line"><__NSArrayI <span class="number">0x60800003f3c0</span>>(</div><div class="line"><span class="number">1</span>,</div><div class="line"><span class="number">2</span></div><div class="line">)</div><div class="line"></div><div class="line"></div><div class="line">(lldb) po [<span class="keyword">self</span>.copiedMutableArray isKindOfClass:[<span class="built_in">NSMutableArray</span> <span class="keyword">class</span>]]</div><div class="line"><span class="literal">NO</span></div><div class="line"></div><div class="line">(lldb) po [<span class="keyword">self</span>.copiedMutableArray isKindOfClass:[<span class="built_in">NSArray</span> <span class="keyword">class</span>]]</div><div class="line"><span class="literal">YES</span></div></pre></td></tr></table></figure><p>可以看出,代码在运行到<code>[self.copiedMutableArray removeObjectAtIndex:0];</code>一行的时候就挂掉了,原因是给<code>NSArray</code>发送<code>removeObjectAtIndex:</code>消息导致<code>unrecognized selector sent to instance</code>。因为作为copy后得到的<code>NSArray</code>对象,并没有任何修改数组的方法。</p><p><code>po</code>一下验证推断:<code>self.copiedMutableArray</code>的确是<code>NSArray</code>类型的对象。<br>证毕。</p><h3 id="4-对集合类对象和非集合类对象的copy与mutableCopy操作"><a href="#4-对集合类对象和非集合类对象的copy与mutableCopy操作" class="headerlink" title="4.对集合类对象和非集合类对象的copy与mutableCopy操作"></a>4.对集合类对象和非集合类对象的<code>copy</code>与<code>mutableCopy</code>操作</h3><p><strong>结论</strong></p><p><strong>非集合对象:</strong></p><table><thead><tr><th>非集合对象</th><th>immutable object</th><th>mutable object</th></tr></thead><tbody><tr><td>copy</td><td>浅拷贝</td><td>深拷贝</td></tr><tr><td>mutableCopy</td><td>深拷贝</td><td>深拷贝</td></tr></tbody></table><p><strong>集合对象:</strong></p><table><thead><tr><th>集合对象</th><th>immutable object</th><th>mutable object</th></tr></thead><tbody><tr><td>copy</td><td>浅拷贝</td><td>单层深拷贝</td></tr><tr><td>mutableCopy</td><td>单层深拷贝</td><td>单层深拷贝</td></tr></tbody></table><p>这里的单层深拷贝是指,对于集合对象,仅复制集合对象本身,并不复制其中的元素,即集合对象是内容复制,其中的对象元素是指针复制。</p><p><strong>做个试验来验证:</strong></p><p><strong>非集合对象:</strong></p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div></pre></td><td class="code"><pre><div class="line"><span class="string">//</span> <span class="string">copy</span> <span class="string">vs</span> <span class="string">mutableCopy</span> <span class="string">(non-collection</span> <span class="string">object)</span></div><div class="line"><span class="string">NSMutableString</span> <span class="meta">*mutableString</span> <span class="string">=</span> <span class="string">[NSMutableString</span> <span class="attr">stringWithString:@"immutableString"];</span></div><div class="line"><span class="string">NSString</span> <span class="meta">*cpString</span> <span class="string">=</span> <span class="string">[mutableString</span> <span class="string">copy];</span></div><div class="line"><span class="string">NSString</span> <span class="meta">*mcpString</span> <span class="string">=</span> <span class="string">[mutableString</span> <span class="string">mutableCopy];</span></div><div class="line"><span class="string">NSString</span> <span class="meta">*sString</span> <span class="string">=</span> <span class="string">mutableString;</span></div><div class="line"><span class="string">[mutableString</span> <span class="attr">appendString:@"</span> <span class="string">copy”];</span></div><div class="line"></div><div class="line"><span class="string">NSLog(@"mutable</span> <span class="attr">copy:</span> <span class="string">\ncopy:</span> <span class="string">%@(%p)\nmutable</span> <span class="attr">copy:</span> <span class="string">%@(%p)\norginal:</span> <span class="string">%@(%p)",</span> <span class="string">cpString,</span> <span class="string">cpString,mcpString,mcpString,</span> <span class="string">sString,sString);</span></div><div class="line"></div><div class="line"><span class="string">NSString</span> <span class="meta">*immutableString</span> <span class="string">=</span> <span class="string">@"immutableString";</span></div><div class="line"><span class="string">NSString</span> <span class="meta">*imcpString</span> <span class="string">=</span> <span class="string">[immutableString</span> <span class="string">copy];</span></div><div class="line"><span class="string">NSString</span> <span class="meta">*immcpString</span> <span class="string">=</span> <span class="string">[immutableString</span> <span class="string">mutableCopy];</span></div><div class="line"></div><div class="line"><span class="string">NSLog(@"immutable</span> <span class="attr">copy:</span> <span class="string">\ncopy:</span> <span class="string">%p\nmutable</span> <span class="attr">copy:</span> <span class="string">%p\norginal:</span> <span class="string">%p",</span> <span class="string">imcpString,</span> <span class="string">immcpString,</span> <span class="string">immutableString);</span></div><div class="line"></div><div class="line"><span class="string">//</span> <span class="attr">Console:</span></div><div class="line"><span class="number">2017</span><span class="bullet">-06</span><span class="bullet">-29</span> <span class="number">13</span><span class="string">:50:41.125</span> <span class="string">iOS</span> <span class="string">Example[71672:37269990]</span> <span class="string">mutable</span> <span class="attr">copy:</span> </div><div class="line"><span class="attr">copy:</span> <span class="string">immutableString(0x600000241620)</span></div><div class="line"><span class="string">mutable</span> <span class="attr">copy:</span> <span class="string">immutableString(0x60000007a600)</span></div><div class="line"><span class="attr">orginal:</span> <span class="string">immutableString</span> <span class="string">copy(0x60000007a640)</span></div><div class="line"><span class="number">2017</span><span class="bullet">-06</span><span class="bullet">-29</span> <span class="number">13</span><span class="string">:50:41.126</span> <span class="string">iOS</span> <span class="string">Example[71672:37269990]</span> <span class="string">immutable</span> <span class="attr">copy:</span> </div><div class="line"><span class="attr">copy:</span> <span class="number">0x105d7b620</span></div><div class="line"><span class="string">mutable</span> <span class="attr">copy:</span> <span class="number">0x618000076e80</span></div><div class="line"><span class="attr">orginal:</span> <span class="number">0x105d7b620</span></div></pre></td></tr></table></figure><p><strong>集合对象:</strong></p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div></pre></td><td class="code"><pre><div class="line">// <span class="keyword">copy</span><span class="bash"> vs mutableCopy (collection object)</span></div><div class="line">NSMutableArray *mutableArray = [NSMutableArray arrayWithObjects:@<span class="number">1</span>, @<span class="number">2</span>, nil];</div><div class="line">NSArray *cpArray = [mutableArray <span class="keyword">copy</span><span class="bash">];</span></div><div class="line">NSArray *mcpArray = [mutableArray mutableCopy];</div><div class="line">NSArray *sArray = mutableArray;</div><div class="line">[mutableArray addObject:@<span class="number">3</span>];</div><div class="line"></div><div class="line">NSLog(@<span class="string">"mutable copy: \ncopy: %@(%p)\nmutable copy: %@(%p)\norginal: %@(%p)"</span>, cpArray, cpArray,mcpArray,mcpArray, sArray,sArray);</div><div class="line"></div><div class="line">NSArray *immutableArray = @[@<span class="number">5</span>, @<span class="number">6</span>];</div><div class="line">NSArray *imcpArray = [immutableArray <span class="keyword">copy</span><span class="bash">];</span></div><div class="line">NSArray *immcpArray = [immutableArray mutableCopy];</div><div class="line"></div><div class="line">NSLog(@<span class="string">"immutable copy: \ncopy: %@(%p)\nmutable copy: %@(%p)\norginal: %@(%p)"</span>, imcpArray, imcpArray,immcpArray,immcpArray, immutableArray,immutableArray);</div><div class="line"></div><div class="line">// Console:</div><div class="line"><span class="number">2017</span>-<span class="number">06</span>-<span class="number">29</span> <span class="number">14</span>:<span class="number">03</span>:<span class="number">00.059</span> iOS Example[<span class="number">71958</span>:<span class="number">37305362</span>] mutable <span class="keyword">copy</span><span class="bash">: </span></div><div class="line"><span class="keyword">copy</span><span class="bash">: (</span></div><div class="line"> <span class="number">1</span>,</div><div class="line"> <span class="number">2</span></div><div class="line">)(<span class="number">0</span>x618000036520)</div><div class="line">mutable <span class="keyword">copy</span><span class="bash">: (</span></div><div class="line"> <span class="number">1</span>,</div><div class="line"> <span class="number">2</span></div><div class="line">)(<span class="number">0</span>x61800005f830)</div><div class="line">orginal: (</div><div class="line"> <span class="number">1</span>,</div><div class="line"> <span class="number">2</span>,</div><div class="line"> <span class="number">3</span></div><div class="line">)(<span class="number">0</span>x61800005f860)</div><div class="line"><span class="number">2017</span>-<span class="number">06</span>-<span class="number">29</span> <span class="number">14</span>:<span class="number">03</span>:<span class="number">00.059</span> iOS Example[<span class="number">71958</span>:<span class="number">37305362</span>] immutable <span class="keyword">copy</span><span class="bash">: </span></div><div class="line"><span class="keyword">copy</span><span class="bash">: (</span></div><div class="line"> <span class="number">5</span>,</div><div class="line"> <span class="number">6</span></div><div class="line">)(<span class="number">0</span>x608000036600)</div><div class="line">mutable <span class="keyword">copy</span><span class="bash">: (</span></div><div class="line"> <span class="number">5</span>,</div><div class="line"> <span class="number">6</span></div><div class="line">)(<span class="number">0</span>x60800005d5e0)</div><div class="line">orginal: (</div><div class="line"> <span class="number">5</span>,</div><div class="line"> <span class="number">6</span></div><div class="line">)(<span class="number">0</span>x608000036600)</div></pre></td></tr></table></figure>]]></content>
<summary type="html">
<h3 id="1-property中的copy关键字如何使用?"><a href="#1-property中的copy关键字如何使用?" class="headerlink" title="1.@property中的copy关键字如何使用?"></a>1.<code>@property</code>中的<code>copy</code>关键字如何使用?</h3><ul>
<li>对于<code>NSString</code>、<code>NSArray</code>、<code>NSDictionary</code>而言:见下面几个问题的回答。</li>
<li>对于block而言:需要使用<code>copy</code>是为了在原有上下文范围外,继续追踪它捕获的状态。在使用ARC的时候不需要担心这个问题,因为是自动进行的,但是最佳实践是为property属性标记上这个必然的行为(即,添加上copy关键字)。</li>
</ul>
</summary>
<category term="iOS" scheme="http://www.caliosd.gq/tags/iOS/"/>
</entry>
<entry>
<title>Issue of lazy loading property</title>
<link href="http://www.caliosd.gq/2017/05/30/issue-of-lazy-loading-property/"/>
<id>http://www.caliosd.gq/2017/05/30/issue-of-lazy-loading-property/</id>
<published>2017-05-30T03:49:09.000Z</published>
<updated>2019-12-17T15:31:51.184Z</updated>
<content type="html"><![CDATA[<p>I’ve got some <code>readonly</code> properties in my .h file. And when I tried to lazy loading them as usual like this.<br><figure class="highlight sqf"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// MyView.h</span></div><div class="line">@property (nonatomic, strong, readonly) UIImageView *imageView;</div><div class="line"></div><div class="line"><span class="comment">// MyView.m</span></div><div class="line">- (UIImageView *)imageView</div><div class="line">{</div><div class="line"> <span class="keyword">if</span> (!<span class="variable">_imageView</span>)</div><div class="line"> {</div><div class="line"> <span class="variable">_imageView</span> = [UIImageView new];</div><div class="line"> <span class="variable">_imageView</span>.translatesAutoresizingMaskIntoConstraints = NO;</div><div class="line"> <span class="variable">_imageView</span>.backgroundColor = [UIColor clearColor];</div><div class="line"> <span class="variable">_imageView</span>.contentMode = UIViewContentModeScaleAspectFit;</div><div class="line"> <span class="variable">_imageView</span>.userInteractionEnabled = NO;</div><div class="line"> <span class="variable">_imageView</span>.accessibilityIdentifier = @<span class="string">"empty set image view"</span>;</div><div class="line"></div><div class="line"> <span class="variable">_imageView</span>.<span class="built_in">image</span> = <span class="variable">_defaultImage</span>;</div><div class="line"></div><div class="line"> [<span class="variable">_contentView</span> addSubview:<span class="variable">_imageView</span>];</div><div class="line"> }</div><div class="line"> return <span class="variable">_imageView</span>;</div><div class="line">}</div></pre></td></tr></table></figure></p><p>Then…bang! It threw out errors in bulk, yelling “Use of undeclared identifier ‘_imageView’”. What the hell have I done!</p><a id="more"></a><p>Luckily, I found some explainations <a href="http://stackoverflow.com/a/13670924/1594792" target="_blank" rel="external">here</a>. It says:</p><blockquote><p>Newer Xcode versions can create a <code>@synthesize</code> statement automatically and use the underscore prefix for instance variables. In this case however, <strong>since the property is read-only and you provide a getter method, Xcode does not synthesize the property automatically.</strong></p></blockquote><p>So, I have to <code>@synthesize</code> the property on my own, and now it’s quiet and cute again. :P</p><hr><p><strong>Update: 2017-05-30</strong></p><p>I happened to recall this issue when clearing up the whole system of Objective-C. Let’s dig deeper about this issue and find if there’s a diamond in it. :P</p><p>This issue has references of two points: <code>@property</code> and <code>@synthesize</code>. Let’s dig more details one by one.</p><h3 id="1-property"><a href="#1-property" class="headerlink" title="1.@property"></a>1.<code>@property</code></h3><p>I’d love to reference this paragraph of <a href="http://rypress.com/tutorials/objective-c/properties" target="_blank" rel="external">Ry’s Objective-C Tutorial</a> to show the goal of @property in Objective-C.</p><blockquote><p>An object’s properties let other objects inspect or change its state. But, in a well-designed object-oriented program, it’s not possible to directly access the internal state of an object. Instead, accessor methods (getters and setters) are used as an abstraction for interacting with the object’s underlying data.<br><img src="http://7xkwcv.com1.z0.glb.clouddn.com/Interacting%20with%20a%20property%20via%20accessor%20methods.png" alt="Interacting with a property via accessor methods"><br>The goal of the @property directive is to make it easy to create and configure properties by automatically generating these accessor methods. It allows you to specify the behavior of a public property on a semantic level, and it takes care of the implementation details for you.</p></blockquote><h3 id="2-synthesize"><a href="#2-synthesize" class="headerlink" title="2.@synthesize"></a>2.<code>@synthesize</code></h3><p>First of all, this feature is called <strong>autosynthesis</strong> of properties and it’s an <a href="http://clang.llvm.org/docs/LanguageExtensions.html#objective-c-autosynthesis-of-properties" target="_blank" rel="external">Objective-C language extension supported by clang</a>, which is the default compiler used by Xcode.</p><p>Thanks to autosynthesis you don’t need to explicitly synthesize the property as it will be automatically synthesized by the compiler as <code>@synthesize propertyName = _propertyName</code>.</p><p>While, there’re some exceptions here:</p><ul><li>Readwrite property with custom getter and setter;</li><li>Readonly property with custom getter;</li><li>@dynamic (which is opposited to <code>@synthesize</code>);</li><li>Properites declared in a @protocol;</li><li>Properties declared in a category;</li><li>Overriden properties.</li></ul><p><img src="http://7xkwcv.com1.z0.glb.clouddn.com/synthesize2.png" alt="Readwrite property with custom getter and setter"></p><p>In this condition, property <code>name</code> is set to be <code>readwrite</code>(as it is by default) with custom getter and setter. At this time, compiler believes that you want to take full control over <code>@property</code> manually, and then forbids autosynthesis for you.</p><p><strong>—Note—</strong> atomic property’s accessor type must be paired, either custom or autosynthesis. Or else, there would be a warning like this.</p><p><img src="http://7xkwcv.com1.z0.glb.clouddn.com/synthesize.png" alt="atomic property's custom accessor must be paired"></p><p><strong>—Note End—</strong></p><p>We’ve already been used to not defining ivars on our own, once you have to use ivar, while autosynthesis is invalid, you’ll have to use <code>@synthesize</code> to compound ivars(i.e. let out the comment for line 24 in the image above).</p><p>Here’s a little sample.</p><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">Car</span> : <span class="title">NSObject</span></span></div><div class="line"></div><div class="line"><span class="keyword">@property</span> (<span class="keyword">strong</span>, <span class="keyword">readwrite</span>) <span class="built_in">NSString</span> *name;</div><div class="line"><span class="keyword">@end</span></div><div class="line"></div><div class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">Car</span></span></div><div class="line"></div><div class="line"><span class="keyword">@end</span></div><div class="line"></div><div class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">Tesla</span> : <span class="title">Car</span></span></div><div class="line"></div><div class="line"><span class="keyword">@property</span> (<span class="keyword">strong</span>, <span class="keyword">readwrite</span>) <span class="built_in">NSString</span> *name;</div><div class="line"></div><div class="line"><span class="keyword">@end</span></div><div class="line"></div><div class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">Tesla</span></span></div><div class="line"><span class="keyword">@synthesize</span> name;</div><div class="line"></div><div class="line">- (<span class="keyword">void</span>)printVar</div><div class="line">{</div><div class="line"><span class="keyword">super</span>.name = <span class="string">@"Car"</span>;</div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"Tesla: Hello %@, ivar: %@,super: %@"</span>, <span class="keyword">self</span>.name, <span class="keyword">self</span>->name, <span class="keyword">super</span>.name);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">@end</span></div></pre></td></tr></table></figure><p>Output in console:</p><figure class="highlight css"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">2017<span class="selector-tag">-06-30</span> 16<span class="selector-pseudo">:02</span><span class="selector-pseudo">:03.946</span> <span class="selector-tag">BlockSample</span><span class="selector-attr">[86278:38771956]</span> <span class="selector-tag">Tesla</span>: <span class="selector-tag">Hello</span> <span class="selector-tag">Tesla</span>, <span class="selector-tag">ivar</span>: <span class="selector-tag">Tesla</span>,<span class="selector-tag">super</span>: <span class="selector-tag">Car</span></div></pre></td></tr></table></figure><p>It’s also fine to get subclass’s ivar <code>name</code> simply by <code>name</code>.</p><h3 id="3-One-more-note-for-dynamic-which-always-appears-hand-in-hand-with-synthesize"><a href="#3-One-more-note-for-dynamic-which-always-appears-hand-in-hand-with-synthesize" class="headerlink" title="3.One more note for @dynamic (which always appears hand in hand with @synthesize =.=)"></a>3.One more note for <code>@dynamic</code> (which always appears hand in hand with @synthesize =.=)</h3><p>This is cited from <a href="http://theocacao.com/document.page/516" target="_blank" rel="external">this article</a>, under the heading “Methods provided at runtime”.</p><blockquote><p>Some accessors are created dynamically at runtime, such as certain ones used in CoreData’s NSManagedObject class. If you want to declare and use properties for these cases, but want to avoid warnings about methods missing at compile time, you can use the <code>@dynamic</code> directive instead of <code>@synthesize</code>.</p><p>Using the <code>@dynamic</code> directive essentially tells the compiler “don’t worry about it, a method is on the way.” :]</p></blockquote>]]></content>
<summary type="html">
<p>I’ve got some <code>readonly</code> properties in my .h file. And when I tried to lazy loading them as usual like this.<br><figure class="highlight sqf"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// MyView.h</span></div><div class="line">@property (nonatomic, strong, readonly) UIImageView *imageView;</div><div class="line"></div><div class="line"><span class="comment">// MyView.m</span></div><div class="line">- (UIImageView *)imageView</div><div class="line">&#123;</div><div class="line"> <span class="keyword">if</span> (!<span class="variable">_imageView</span>)</div><div class="line"> &#123;</div><div class="line"> <span class="variable">_imageView</span> = [UIImageView new];</div><div class="line"> <span class="variable">_imageView</span>.translatesAutoresizingMaskIntoConstraints = NO;</div><div class="line"> <span class="variable">_imageView</span>.backgroundColor = [UIColor clearColor];</div><div class="line"> <span class="variable">_imageView</span>.contentMode = UIViewContentModeScaleAspectFit;</div><div class="line"> <span class="variable">_imageView</span>.userInteractionEnabled = NO;</div><div class="line"> <span class="variable">_imageView</span>.accessibilityIdentifier = @<span class="string">"empty set image view"</span>;</div><div class="line"></div><div class="line"> <span class="variable">_imageView</span>.<span class="built_in">image</span> = <span class="variable">_defaultImage</span>;</div><div class="line"></div><div class="line"> [<span class="variable">_contentView</span> addSubview:<span class="variable">_imageView</span>];</div><div class="line"> &#125;</div><div class="line"> return <span class="variable">_imageView</span>;</div><div class="line">&#125;</div></pre></td></tr></table></figure></p>
<p>Then…bang! It threw out errors in bulk, yelling “Use of undeclared identifier ‘_imageView’”. What the hell have I done!</p>
</summary>
<category term="iOS" scheme="http://www.caliosd.gq/tags/iOS/"/>
</entry>
<entry>
<title>KVO & KVC 知识点小结</title>
<link href="http://www.caliosd.gq/2017/05/12/kvo-kvc-missing-points/"/>
<id>http://www.caliosd.gq/2017/05/12/kvo-kvc-missing-points/</id>
<published>2017-05-12T05:25:43.000Z</published>
<updated>2018-09-15T08:14:16.000Z</updated>
<content type="html"><![CDATA[<h3 id="1-KVO"><a href="#1-KVO" class="headerlink" title="1.KVO"></a>1.KVO</h3><h4 id="1-1-属性依赖的机制"><a href="#1-1-属性依赖的机制" class="headerlink" title="1.1 属性依赖的机制"></a>1.1 属性依赖的机制</h4><p>在objc.io讲解KVC和KVO的一篇<a href="https://www.objc.io/issues/7-foundation/key-value-coding-and-observing/" target="_blank" rel="external">文章</a>中,举了一个体现属性依赖机制的例子,例子的逻辑结构如下图。完整代码见<a href="https://github.com/objcio/issue-7-lab-color-space-explorer" target="_blank" rel="external">github</a>。</p><a id="more"></a><p><img src="http://7xkwcv.com1.z0.glb.clouddn.com/1.1%20PropertyDependency.png" alt="属性依赖机制"></p><p><strong>图解:</strong></p><ul><li>该图表现了通过改变L、a、b的滑块,更新上图右侧view的背景颜色的实现原理。</li><li>L、a、b代表Lab色彩空间中颜色的三种影响因素。</li></ul><figure class="highlight lisp"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">+ (<span class="name">NSSet<NSString</span> *>) keyPathsForValuesAffectingValueForKey: (NSString *)key<span class="comment">;</span></div></pre></td></tr></table></figure><ul><li>key:受key path影响的key。</li><li>return:返回影响指定key的一组key path。</li><li>使用:<code>+ (NSSet<NSString *>) keyPathsForValuesAffecting<Key></code>。</li></ul><p><strong>详解:</strong></p><ul><li>red的变化受L影响,green变化受L和a影响,blue变化受L和b影响,最终red、green和blue共同决定color属性,即view显示的背景颜色。</li><li>通过实现对应的<code>+keyPathsForValuesAffectingRedComponent/GreenComponent/BlueComponent/Color</code>来指定各个属性之间的依赖关系。</li><li>添加属性后,会在代码提示中自动生成如下名称的方法,选择一个就好了。</li></ul><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div></pre></td><td class="code"><pre><div class="line">+ (<span class="built_in">NSSet</span> *)keyPathsForValuesAffectingRedComponent</div><div class="line">{</div><div class="line"> <span class="keyword">return</span> [<span class="built_in">NSSet</span> setWithObject:<span class="string">@"lComponent"</span>];</div><div class="line">}</div><div class="line"></div><div class="line">+ (<span class="built_in">NSSet</span> *)keyPathsForValuesAffectingGreenComponent</div><div class="line">{</div><div class="line"> <span class="keyword">return</span> [<span class="built_in">NSSet</span> setWithObjects:<span class="string">@"lComponent"</span>, <span class="string">@"aComponent"</span>, <span class="literal">nil</span>];</div><div class="line">}</div><div class="line"></div><div class="line">+ (<span class="built_in">NSSet</span> *)keyPathsForValuesAffectingBlueComponent</div><div class="line">{</div><div class="line"> <span class="keyword">return</span> [<span class="built_in">NSSet</span> setWithObjects:<span class="string">@"lComponent"</span>, <span class="string">@"bComponent"</span>, <span class="literal">nil</span>];</div><div class="line">}</div><div class="line"></div><div class="line">+ (<span class="built_in">NSSet</span> *)keyPathsForValuesAffectingColor</div><div class="line">{</div><div class="line"> <span class="keyword">return</span> [<span class="built_in">NSSet</span> setWithObjects:<span class="string">@"redComponent"</span>, <span class="string">@"greenComponent"</span>, <span class="string">@"blueComponent"</span>, <span class="literal">nil</span>];</div><div class="line">}</div></pre></td></tr></table></figure><h4 id="1-2-KVO"><a href="#1-2-KVO" class="headerlink" title="1.2 KVO"></a>1.2 KVO</h4><p>添加对LabColor的实例对象labColor的属性color的观察者,并添加相应的响应事件,更新view的backgroundColor。</p><figure class="highlight erlang"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line">- <span class="params">(void)</span>addObserver:<span class="params">(NSObject *)</span>anObserver</div><div class="line"> forKeyPath:<span class="params">(NSString *)</span>keyPath</div><div class="line"> options:<span class="params">(NSKeyValueObservingOptions)</span>options</div><div class="line"> context:<span class="params">(void *)</span>context</div><div class="line">- <span class="params">(void)</span>observeValueForKeyPath:<span class="params">(NSString *)</span>keyPath</div><div class="line"> ofObject:<span class="params">(id)</span>object</div><div class="line"> change:<span class="params">(NSDictionary *)</span>change</div><div class="line"> context:<span class="params">(void *)</span>context</div></pre></td></tr></table></figure><p>对于options的几个可选值:</p><ul><li>获取变化之前的值用NSKeyValueObservingOptionOld,获取变化之后的值用NSKeyValueObservingOptionNew,二者都取用按位或。</li><li>在添加观察者之前就立即发送变化的通知用NSKeyValueObservingOptionInitial,可以通过这种一次性的通知确定被观察者某属性的初始值。</li><li>在即将发生变化之前发送通知用NSKeyValueObservingOptionPrior(通常都是发生变化后发送通知)。<br>建议设置Context,避免子类和父类观察同一对象的同一属性。</li></ul><p><strong>注意:</strong> KVO的add方法并不对观察对象、被观察对象和context持有强引用,所以要自行确保对于观察对象、被观察对象和context的强引用。</p><h4 id="1-3-手动通知"><a href="#1-3-手动通知" class="headerlink" title="1.3 手动通知"></a>1.3 手动通知</h4><ul><li>如何手动通知?</li></ul><p>当我们需要override 属性的setter方法时,有时候需要添加一些自定义的控制,再进行赋值。这时需要关闭系统自动调用 <code>-willChangeValueForKey:</code>和 <code>-didChangeValueForKey:</code>的行为,改为手动调用这两个方法。</p><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line">+ (<span class="built_in">BOOL</span>)automaticallyNotifiesObserversForLComponent;</div><div class="line">{</div><div class="line"> <span class="keyword">return</span> <span class="literal">NO</span>;</div><div class="line">}</div><div class="line"></div><div class="line">- (<span class="keyword">void</span>)setLComponent:(<span class="keyword">double</span>)lComponent;</div><div class="line">{</div><div class="line"> <span class="keyword">if</span> (_lComponent == lComponent) {</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"> [<span class="keyword">self</span> willChangeValueForKey:<span class="string">@"lComponent"</span>];</div><div class="line"> _lComponent = lComponent;</div><div class="line"> [<span class="keyword">self</span> didChangeValueForKey:<span class="string">@"lComponent"</span>];</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>何时需要手动通知?<ul><li>使用 <code>-will|didChangeValueForKey:</code>的正确方式是,当你没有通过KVO兼容的accessor或setter改变property时,这种情况下KVO机制是不会捕获到变化的,需要手动触发。</li><li>只要触发setter,即使当前值没有改变,也会产生KVO的通知。当处于效率考虑、希望避免这种无用通知时可以考虑手动通知。</li></ul></li></ul><h4 id="1-4-KVO与线程"><a href="#1-4-KVO与线程" class="headerlink" title="1.4 KVO与线程"></a>1.4 KVO与线程</h4><p>通常来说,不推荐把KVO和多线程混合使用。因为KVO的行为是同步的,发生变化的线程和处理变化的线程应该是同一个线程。如果要使用多队列或是线程,我们不应该跨队列或是跨线程使用KVO。</p><h4 id="1-5-KVO实现细节"><a href="#1-5-KVO实现细节" class="headerlink" title="1.5 KVO实现细节"></a>1.5 KVO实现细节</h4><p>自动的KVO是通过一种叫<code>isa-swizzling</code>的技术实现的。isa指针通常是指向对象的类,类的分发表中包含着指向该类实现的方法的一些指针。<br>当观察者被注册为要观察对象的某个属性时,被观察者对象isa指针就被改变了,它不再指向实际的类,而是指向一个中间类。因此isa指针并不能反映实例所属的真正类。如果想要获取实例对象的类,应该使用class方法。</p><p>(然而,在Mike Ash一篇<a href="https://www.mikeash.com/pyblog/friday-qa-2009-01-23.html" target="_blank" rel="external">谈论KVO在runtime层面如何实现的文章</a>中谈到,当你第一次观察指定类的对象时,KVO会在runtime创建一个全新的、继承自你的class的子类。在这个全新的类中,它override了任何被观察key的set方法,然后把你的对象的isa指针转移,这样你的对象就成为了这个新类的实例。被override的方法正是通知观察者实现的本质。逻辑是这样的:对于key的改变必须走key的set方法。通过override它的set方法,就可以劫持它,并且在它被调用的时候发送通知给观察者。苹果公司实在是不想将这个机制暴露出来,因此,除了setter之外,那个动态的子类还override了<code>-class</code>方法,来向你返回原始的类。如果你不仔细研究的话,KVO改变的对象就像没有被观察一样。私以为,Mike说得更接近真相。毕竟,真相只有一个。:P)</p><h4 id="1-6-KVO相关API"><a href="#1-6-KVO相关API" class="headerlink" title="1.6 KVO相关API"></a>1.6 KVO相关API</h4><p><img src="http://7xkwcv.com1.z0.glb.clouddn.com/KVO%20API.png" alt="KVO API"></p><h3 id="2-KVC"><a href="#2-KVC" class="headerlink" title="2.KVC"></a>2.KVC</h3><h4 id="2-1-不需要-property的KVC"><a href="#2-1-不需要-property的KVC" class="headerlink" title="2.1 不需要@property的KVC"></a>2.1 不需要@property的KVC</h4><ul><li>直接添加 <code>-<key></code> 和 <code>-set<Key>:</code> 方法;</li><li>要正确处理nil,需要override <code>-setNilValueForKey:</code> 方法;</li><li>还可以通过override如下方法来让一个类支持KVC,但是会影响性能。</li></ul><figure class="highlight erlang"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">- <span class="params">(id)</span>valueForUndefinedKey:<span class="params">(NSString *)</span>key;</div><div class="line">- <span class="params">(void)</span>setValue:<span class="params">(id)</span>value forUndefinedKey:<span class="params">(NSString *)</span>key;</div></pre></td></tr></table></figure><ul><li>Foundation 框架支持直接访问实例变量。请小心的使用这个特性。你可以去查看 <code>+accessInstanceVariablesDirectly</code> 的文档。这个值默认是 <code>YES</code> 的时候,Foundation 会按照 <code>_<key></code>, <code>_is<Key></code>, <code><key></code>和 <code>is<Key></code> 的顺序查找实例变量。</li></ul><h4 id="2-2-通过集合代理对象实现KVC"><a href="#2-2-通过集合代理对象实现KVC" class="headerlink" title="2.2 通过集合代理对象实现KVC"></a>2.2 通过集合代理对象实现KVC</h4><h4 id="2-3-常见错误"><a href="#2-3-常见错误" class="headerlink" title="2.3 常见错误"></a>2.3 常见错误</h4><ul><li>KVO 旨在观察 <em>关系 (relationship)</em> 而不是集合。我们不能观察 NSArray,我们只能观察一个对象的属性——而这个属性有可能是 NSArray。相似地,观察 self 不是永远都生效的。而且这不是一个好的设计。</li></ul><h4 id="2-4-KVV(键值验证)"><a href="#2-4-KVV(键值验证)" class="headerlink" title="2.4 KVV(键值验证)"></a>2.4 KVV(键值验证)</h4><ul><li>KVV 也是 KVC API 的一部分。这是一个用来验证属性值的 API,只是它光靠自己很难提供逻辑和功能。</li><li>用 KVV 验证 model 类的值是 Cocoa 的惯例。</li><li>需要在model中提供 <code>-validate<Key>:error:</code>方法。</li></ul><h4 id="2-5-KVC相关API"><a href="#2-5-KVC相关API" class="headerlink" title="2.5 KVC相关API"></a>2.5 KVC相关API</h4><p><img src="http://7xkwcv.com1.z0.glb.clouddn.com/KVC%20API.png" alt="KVC API"></p><h4 id="2-6-valueForKey-实现过程"><a href="#2-6-valueForKey-实现过程" class="headerlink" title="2.6 -valueForKey: 实现过程"></a>2.6 <code>-valueForKey:</code> 实现过程</h4><p><img src="http://7xkwcv.com1.z0.glb.clouddn.com/KVC%20Process.jpg" alt="valueForKey:"></p><h3 id="Ref"><a href="#Ref" class="headerlink" title="Ref:"></a>Ref:</h3><ul><li><a href="https://www.objc.io/issues/7-foundation/key-value-coding-and-observing/" target="_blank" rel="external">https://www.objc.io/issues/7-foundation/key-value-coding-and-observing/</a></li><li><a href="https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueCoding/index.html#//apple_ref/doc/uid/10000107-SW1" target="_blank" rel="external">https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueCoding/index.html#//apple_ref/doc/uid/10000107-SW1</a></li><li><a href="https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html" target="_blank" rel="external">https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html</a></li><li><a href="https://stackoverflow.com/q/3018242/1594792" target="_blank" rel="external">https://stackoverflow.com/q/3018242/1594792</a></li><li><a href="https://www.mikeash.com/pyblog/friday-qa-2009-01-23.html" target="_blank" rel="external">https://www.mikeash.com/pyblog/friday-qa-2009-01-23.html</a></li></ul>]]></content>
<summary type="html">
<h3 id="1-KVO"><a href="#1-KVO" class="headerlink" title="1.KVO"></a>1.KVO</h3><h4 id="1-1-属性依赖的机制"><a href="#1-1-属性依赖的机制" class="headerlink" title="1.1 属性依赖的机制"></a>1.1 属性依赖的机制</h4><p>在objc.io讲解KVC和KVO的一篇<a href="https://www.objc.io/issues/7-foundation/key-value-coding-and-observing/" target="_blank" rel="external">文章</a>中,举了一个体现属性依赖机制的例子,例子的逻辑结构如下图。完整代码见<a href="https://github.com/objcio/issue-7-lab-color-space-explorer" target="_blank" rel="external">github</a>。</p>
</summary>
<category term="iOS" scheme="http://www.caliosd.gq/tags/iOS/"/>
</entry>
<entry>
<title>0422-美团沙龙面基</title>
<link href="http://www.caliosd.gq/2017/04/23/essay-meituan-salon-170422/"/>
<id>http://www.caliosd.gq/2017/04/23/essay-meituan-salon-170422/</id>
<published>2017-04-23T05:32:43.000Z</published>
<updated>2018-09-15T09:34:22.000Z</updated>
<content type="html"><![CDATA[<p>一个兴起参加了美团周末的技术沙龙,技术上的收获不敢说有多少,感想倒是有一些。</p><h3 id="所谓长见识,无非是“走出去”和“走进来”"><a href="#所谓长见识,无非是“走出去”和“走进来”" class="headerlink" title="所谓长见识,无非是“走出去”和“走进来”"></a>所谓长见识,无非是“走出去”和“走进来”</h3><p>做小app做惯了,难免井底观天,不知移动开发的深浅了。只有当面向用户量数以万计、十万、百万的app时,一些宏观的问题才会浮出水面。比如说系统模块化的分割,比如说代码的多端复用,比如说性能监控方案。往大了说,这应该是应用系统普遍会遇到的问题,而不仅仅是移动端;往小了说,一个简单的app,如果想从这些方面进行优化,也是需要费一些心思的。</p><p>这大概,是我第一次切身地意识到,在大厂工作,是站在巨人的肩膀上这件事吧。虽说用户量高的app不一定是大厂,但大厂的用户量一定不少。</p><h3 id="所谓社交,无非是踏出自己的舒适区"><a href="#所谓社交,无非是踏出自己的舒适区" class="headerlink" title="所谓社交,无非是踏出自己的舒适区"></a>所谓社交,无非是踏出自己的舒适区</h3><p>第一次与黑魔法的同学们线下见面,虽然之前有一点小害羞,但见到之后也就没感觉了。互相交流一下对于某个话题的看法,更多的是听他们聊某个自己不甚熟悉的点。终于有一种同伴的感觉。这是对于一直独处的我弥足珍贵的。</p><p>想起之前在摄影群,自己也是通过思维导图打开一点局面的,这次也是。可能,这是可以从自己身上深入挖掘的一个点吧。</p>]]></content>
<summary type="html">
<p>一个兴起参加了美团周末的技术沙龙,技术上的收获不敢说有多少,感想倒是有一些。</p>
<h3 id="所谓长见识,无非是“走出去”和“走进来”"><a href="#所谓长见识,无非是“走出去”和“走进来”" class="headerlink" title="所谓长见识,
</summary>
<category term="ThoughtInTech" scheme="http://www.caliosd.gq/tags/ThoughtInTech/"/>
</entry>
<entry>
<title>WebSocket初探</title>
<link href="http://www.caliosd.gq/2017/04/19/first-shot-in-WebSocket/"/>
<id>http://www.caliosd.gq/2017/04/19/first-shot-in-WebSocket/</id>
<published>2017-04-19T06:56:15.000Z</published>
<updated>2019-12-17T15:32:18.333Z</updated>
<content type="html"><![CDATA[<h3 id="1-基础概要"><a href="#1-基础概要" class="headerlink" title="1.基础概要"></a>1.基础概要</h3><p><strong>应用层</strong>,主要解决如何包装数据:HTTP、FTP、Telnet等。其中,HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。所以是<strong>短连接</strong>。相对而言,socket连接是<strong>长连接</strong>,两端一旦建立连接将不会主动断掉。但由于各种环境因素可能会断开,所以当一个socket连接中没有数据传输时,为了维持连接需要发送<strong>心跳消息</strong>。<br><strong>传输层</strong>,主要解决数据如何在网络中传输:TCP协议。<br><strong>网络层</strong>,主要解决数据如何在网络中传输:IP协议。</p><a id="more"></a><blockquote><p>心跳消息(Heartbeat Message):是一种发送源发送到接收方的消息,这种消息可以让接收方确定发送源是否以及何时出现故障或终止。常用于高可用性或容错处理的目的。</p></blockquote><p>socket是对TCP/IP协议的封装和应用,本身<strong><strong>并不是协议</strong></strong>,<strong><strong>而是一个调用接口(API)</strong></strong>,通过socket,我们才能使用TCP/IP协议。</p><p>“TCP/IP只是一个协议栈,就像操作系统的运行机制一样,必须要具体实现,同时还要提供对外的操作接口。就像操作系统会提供标准的编程接口,比如win32编程接口一样,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口。”</p><blockquote><p><strong>TCP连接的三次握手和四次分手:</strong><br>来自网络的流程图:<br><img src="http://7xkwcv.com1.z0.glb.clouddn.com/TCP3344.jpg" alt="TCP3344"></p><p><strong>TCP与UDP的区别:</strong></p><ul><li>TCP是面向连接的,保证连接的可靠性;</li><li>UDP在传送数据之前并不与对方建立连接,对收到的数据也不发送确认信号,是无连接的、不可靠的数据传输协议。</li><li>MSN采用TCP协议,QQ采用UDP,所以后者更快一些。</li></ul></blockquote><h3 id="2-利用Socket建立网络连接的步骤"><a href="#2-利用Socket建立网络连接的步骤" class="headerlink" title="2.利用Socket建立网络连接的步骤"></a>2.利用Socket建立网络连接的步骤</h3><p>ClientSocket,ServerSocket</p><p>套接字之间的连接步骤:</p><ul><li>服务器监听:处于等待客户端连接请求的状态,实时监控网络状态。</li><li>客户端请求:ClientSocket必须首先描述它要连接的ServerSocket,指出ServerSocket的地址和端口号,然后提出连接请求。</li><li>连接确认:当ServerSocket接收到ClientSocket的连接请求时,就响应请求,建立一个新的线程,把ServerSocket的描述发给ClientSocket,一旦Client确认了此描述,双方就正式建立连接。而ServerSocket继续处于监听状态,继续接收其他ClientSocket的连接请求。</li></ul><h3 id="3-WebSocket"><a href="#3-WebSocket" class="headerlink" title="3.WebSocket"></a>3.WebSocket</h3><p>WebSocket是HTML5开始提供的一种浏览器和服务器间进行<strong>全双工通讯</strong>的网络技术。在WebSocket API中,浏览器和服务器只需要做一个握手的动作,就可以形成一条快速通道,二者就可以直接进行数据互传。</p><blockquote><p>全双工:通讯传输的术语。指可以同时(瞬时)进行信号的双向传输。与之相对,单工就是只允许甲方向乙方传送信息,而乙方不能向甲方传送。</p></blockquote><p>面对这种情况,HTML5定义了WebSocket协议,节省服务器资源和带宽,并达到实时通讯。</p><p>具体规范见<a href="https://tools.ietf.org/html/rfc6455#section-4" target="_blank" rel="external">官网的RFC 6455文档</a>及<a href="https://www.gitbook.com/book/chenjianlong/rfc-6455-websocket-protocol-in-chinese/details" target="_blank" rel="external">翻译版本</a>。</p><p>客户端的WebSocket对象共绑定了四个事件:</p><ul><li>onopen:连接建立时触发;</li><li>onmessage:收到服务器消息时触发;</li><li>onerror:连接出错时触发;</li><li>onclose:连接关闭时触发。</li></ul><blockquote><p>socket与websocket的差别:</p><ul><li>socket是更底层。</li><li>websocket是在普通的socket的基础上添加一些framing和一次http兼容的握手机制。这个http兼容的握手机制只是为了允许websocket在webserver运行的同一个端口上进行连接,但一旦连接建立,webserver就不再loop中了。</li></ul></blockquote><h3 id="4-IM即时聊天的解决方案"><a href="#4-IM即时聊天的解决方案" class="headerlink" title="4.IM即时聊天的解决方案"></a>4.IM即时聊天的解决方案</h3><p>IM的主流技术:</p><ul><li>http polling:即轮询,是在特定的时间间隔(如1秒),由浏览器对服务器发出HTTP request,然后由服务器返回最新的数据给客户端的浏览器。缺点是,浏览器不断向服务器发出请求,HTTP request的header是很长的,里面包含的数据可能很小,占用带宽和服务器资源。</li></ul><p>来自网络的流程图:<br><img src="http://7xkwcv.com1.z0.glb.clouddn.com/4.1%20Polling" alt="polling"></p><ul><li>http long-polling:即长轮询,又称comet。当server端没有数据推送到client端时,请求不会立即返回,而是被server端hold住,直到有数据发送,或者超时,才发送响应。client收到响应之后,立即重新发起http请求。</li></ul><p>来自网络的流程图:<br><img src="http://7xkwcv.com1.z0.glb.clouddn.com/4.2%20Long%20Polling" alt="long polling"></p><ul><li>socket长连接。</li></ul><p>Socket开源框架:<a href="https://github.com/robbiehanson/CocoaAsyncSocket" target="_blank" rel="external">CocoaAsyncSocket</a>,<a href="https://github.com/socketio/socket.io-client-swift" target="_blank" rel="external">socketio/socket.io-client-swift</a><br>WebSocket开源框架:<a href="https://github.com/facebook/SocketRocket" target="_blank" rel="external">facebook/SocketRocket</a>,<a href="https://github.com/tidwall/SwiftWebSocket" target="_blank" rel="external">tidwall/SwiftWebSocket</a><br>UI方面开源框架:<a href="https://github.com/jessesquires/JSQMessagesViewController" target="_blank" rel="external">JSQMessagesViewController</a></p><p>第三方SDK集成:</p><ul><li>前期:环信,容联云(集成了聊天、视频、语音)。</li><li>Firebase:<a href="https://firebase.google.com/(已墙。。bye。。。)" target="_blank" rel="external">https://firebase.google.com/(已墙。。bye。。。)</a></li><li>野狗:<a href="https://www.wilddog.com/(小团队,坐标望京)" target="_blank" rel="external">https://www.wilddog.com/(小团队,坐标望京)</a></li><li>微信用的WebRTC。</li></ul><p>其他协议:</p><ul><li>MQTT:是一个客户端服务端架构的发布/订阅模式的消息传输协议。知名的IM移动app,应该都是用的这个。听说XMPP到一定并发量有天坑。。?</li><li>XMPP:是一种以XML为基础的开放式即时通信协议。</li></ul><h3 id="5-iOS端与web端通过socket建立通讯"><a href="#5-iOS端与web端通过socket建立通讯" class="headerlink" title="5.iOS端与web端通过socket建立通讯"></a>5.iOS端与web端通过socket建立通讯</h3><p>web端:使用的是nodejs的socket.io。<br>iOS端:使用的是<a href="https://github.com/socketio/socket.io-client-swift" target="_blank" rel="external">socket.io的Swift版本</a>。</p><h5 id="5-1-web端配置"><a href="#5-1-web端配置" class="headerlink" title="5.1 web端配置"></a>5.1 web端配置</h5><ul><li>安装node.js:<a href="https://nodejs.org/en/" target="_blank" rel="external">https://nodejs.org/en/</a></li><li>安装node.js的web框架express:<code>npm install —save [email protected]</code></li><li>创建index.js并添加如下代码:</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> app = <span class="built_in">require</span>(<span class="string">'express'</span>)();</div><div class="line"><span class="keyword">var</span> http = <span class="built_in">require</span>(<span class="string">'http'</span>).Server(app);</div><div class="line"></div><div class="line">app.get(<span class="string">'/'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>)</span>{</div><div class="line"> res.send(<span class="string">'<h1>Hello world</h1>'</span>);</div><div class="line">});</div><div class="line"></div><div class="line">http.listen(<span class="number">3000</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="string">'listening on *:3000'</span>);</div><div class="line">});</div></pre></td></tr></table></figure><ul><li><p>运行 <code>node index.js</code>,terminal显示正在监听3000端口。<br><img src="http://7xkwcv.com1.z0.glb.clouddn.com/5.1.1%20run%20node" alt="run node"></p></li><li><p>浏览器访问<a href="http://localhost:3000。" target="_blank" rel="external">http://localhost:3000。</a><br><img src="http://7xkwcv.com1.z0.glb.clouddn.com/5.1%20visit%20localhost" alt="visit localhost"></p></li><li><p>添加显示对话窗口的html页面。</p></li></ul><figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div></pre></td><td class="code"><pre><div class="line"><span class="meta"><!doctype html></span></div><div class="line"><span class="tag"><<span class="name">html</span>></span></div><div class="line"> <span class="tag"><<span class="name">head</span>></span></div><div class="line"> <span class="tag"><<span class="name">title</span>></span>Socket.IO chat<span class="tag"></<span class="name">title</span>></span></div><div class="line"> <span class="tag"><<span class="name">style</span>></span><span class="css"></span></div><div class="line"> * { <span class="attribute">margin</span>: <span class="number">0</span>; <span class="attribute">padding</span>: <span class="number">0</span>; <span class="attribute">box-sizing</span>: border-box; }</div><div class="line"> <span class="selector-tag">body</span> { <span class="attribute">font</span>: <span class="number">13px</span> Helvetica, Arial; }</div><div class="line"> <span class="selector-tag">form</span> { <span class="attribute">background</span>: <span class="number">#000</span>; <span class="attribute">padding</span>: <span class="number">3px</span>; <span class="attribute">position</span>: fixed; <span class="attribute">bottom</span>: <span class="number">0</span>; <span class="attribute">width</span>: <span class="number">100%</span>; }</div><div class="line"> <span class="selector-tag">form</span> <span class="selector-tag">input</span> { <span class="attribute">border</span>: <span class="number">0</span>; <span class="attribute">padding</span>: <span class="number">10px</span>; <span class="attribute">width</span>: <span class="number">90%</span>; <span class="attribute">margin-right</span>: .<span class="number">5%</span>; }</div><div class="line"> <span class="selector-tag">form</span> <span class="selector-tag">button</span> { <span class="attribute">width</span>: <span class="number">9%</span>; <span class="attribute">background</span>: <span class="built_in">rgb</span>(130, 224, 255); <span class="attribute">border</span>: none; <span class="attribute">padding</span>: <span class="number">10px</span>; }</div><div class="line"> <span class="selector-id">#messages</span> { <span class="attribute">list-style-type</span>: none; <span class="attribute">margin</span>: <span class="number">0</span>; <span class="attribute">padding</span>: <span class="number">0</span>; }</div><div class="line"> <span class="selector-id">#messages</span> <span class="selector-tag">li</span> { <span class="attribute">padding</span>: <span class="number">5px</span> <span class="number">10px</span>; }</div><div class="line"> <span class="selector-id">#messages</span> <span class="selector-tag">li</span><span class="selector-pseudo">:nth-child(odd)</span> { <span class="attribute">background</span>: <span class="number">#eee</span>; }</div><div class="line"> <span class="tag"></<span class="name">style</span>></span></div><div class="line"> <span class="tag"></<span class="name">head</span>></span></div><div class="line"> <span class="tag"><<span class="name">body</span>></span></div><div class="line"> <span class="tag"><<span class="name">ul</span> <span class="attr">id</span>=<span class="string">"messages"</span>></span><span class="tag"></<span class="name">ul</span>></span></div><div class="line"> <span class="tag"><<span class="name">form</span> <span class="attr">action</span>=<span class="string">""</span>></span></div><div class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">id</span>=<span class="string">"m"</span> <span class="attr">autocomplete</span>=<span class="string">"off"</span> /></span><span class="tag"><<span class="name">button</span>></span>Send<span class="tag"></<span class="name">button</span>></span></div><div class="line"> <span class="tag"></<span class="name">form</span>></span></div><div class="line"> <span class="tag"></<span class="name">body</span>></span></div><div class="line"><span class="tag"></<span class="name">html</span>></span></div></pre></td></tr></table></figure><ul><li>安装socket.io:<code>npm install —save socket.io</code>。</li><li><p>将socket相关的逻辑已更新到sample的<code>index.js</code>和<code>index.html</code>中。</p></li><li><p>最终,两个浏览器窗口之间,可以进行实时接发消息,web端配置就完成了。</p></li></ul><h5 id="5-2-iOS端配置"><a href="#5-2-iOS端配置" class="headerlink" title="5.2 iOS端配置"></a>5.2 iOS端配置</h5><figure class="highlight shell"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><span class="meta">$</span><span class="bash"> <span class="built_in">cd</span> WebSocketSample/ClientWebSocket</span></div><div class="line"><span class="meta">$</span><span class="bash"> open SocketChat.xcodeproj</span></div></pre></td></tr></table></figure><p>项目完整代码见Github: <a href="https://github.com/CaliosD/WebSocketSample" target="_blank" rel="external">https://github.com/CaliosD/WebSocketSample</a></p><h3 id="6-参考文档"><a href="#6-参考文档" class="headerlink" title="6.参考文档"></a>6.参考文档</h3><ul><li><a href="https://socket.io/get-started/chat/" target="_blank" rel="external">https://socket.io/get-started/chat/</a></li><li><a href="http://www.appcoda.com/socket-io-chat-app/" target="_blank" rel="external">http://www.appcoda.com/socket-io-chat-app/</a></li></ul>]]></content>
<summary type="html">
<h3 id="1-基础概要"><a href="#1-基础概要" class="headerlink" title="1.基础概要"></a>1.基础概要</h3><p><strong>应用层</strong>,主要解决如何包装数据:HTTP、FTP、Telnet等。其中,HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。所以是<strong>短连接</strong>。相对而言,socket连接是<strong>长连接</strong>,两端一旦建立连接将不会主动断掉。但由于各种环境因素可能会断开,所以当一个socket连接中没有数据传输时,为了维持连接需要发送<strong>心跳消息</strong>。<br><strong>传输层</strong>,主要解决数据如何在网络中传输:TCP协议。<br><strong>网络层</strong>,主要解决数据如何在网络中传输:IP协议。</p>
</summary>
<category term="iOS" scheme="http://www.caliosd.gq/tags/iOS/"/>
</entry>
<entry>
<title>Regex Notes</title>
<link href="http://www.caliosd.gq/2017/04/15/notes-for-regex/"/>
<id>http://www.caliosd.gq/2017/04/15/notes-for-regex/</id>
<published>2017-04-15T07:33:47.000Z</published>
<updated>2018-09-15T08:14:51.000Z</updated>
<content type="html"><![CDATA[<h3 id="Some-definitions-before-we-start"><a href="#Some-definitions-before-we-start" class="headerlink" title="Some definitions before we start:"></a>Some definitions before we start:</h3><ul><li><strong>literal</strong>: A <strong>literal</strong> is any character we use in a search or matching expression, for example, to find <strong>ind</strong> in w <strong>ind</strong> ows the <strong>ind</strong> is a <strong>literal</strong> string - each character plays a part in the search, it is <strong>literally</strong> the string we want to find.</li></ul><a id="more"></a><ul><li><strong>metacharacter</strong>: A <strong>metacharacter</strong> is one or more special characters that have a unique meaning and are NOT used as <strong>literals</strong> in the search expression, for example, the character ^ (circumflex or caret) is a <strong>metacharacter</strong>.</li><li><strong>target string</strong>: This term describes the string that we will be searching, that is, the string in which we want to find our match or search pattern.</li><li><strong>search expression</strong>: Most commonly called the regular expression. This term describes the search expression that we will be using to search our target string, that is, the pattern we use to find what we want.</li><li><strong>escape sequence</strong>: An <strong>escape sequence</strong> is a way of indicating that we want to use one of our <strong>metacharacters</strong> as a <strong>literal</strong>. In a regular expression an <strong>escape sequence</strong> involves placing the <strong>metacharacter</strong> <code>\</code> (backslash) in front of the <strong>metacharacter</strong> that we want to use as a <strong>literal</strong>, for example, if we want to find <strong>(s)</strong> in the target string <strong>window(s)</strong> then we use the search expression <code>\(s\)</code> and if we want to find <code>\\file</code> in the target string <code>:\\file</code> then we would need to use the search expression <code>\\\\file</code> (each <code>\</code> we want to search for as a <strong>literal</strong> (there are 2) is preceded by an <strong>escape sequence )</strong>.</li></ul><h3 id="1-Brackets-Ranges-and-Negation"><a href="#1-Brackets-Ranges-and-Negation" class="headerlink" title="1.Brackets, Ranges and Negation"></a>1.Brackets, Ranges and Negation</h3><ul><li><code>[ ]</code> : Match anything inside the square brackets for <strong>ONE character position, once and only once</strong>.</li><li><code>-</code> : The - (dash) <strong>inside square brackets</strong> is the ‘range separator’ and allows us to define a range.<ul><li>Note: - inside brackets(as a literal) must come first or last.</li></ul></li><li><code>^</code> : The ^ (circumflex or caret) <strong>inside square brackets</strong> negates the expression.<ul><li>Note: There are no spaces between the range delimiter values.</li></ul></li></ul><h3 id="2-Positioning-or-Anchors"><a href="#2-Positioning-or-Anchors" class="headerlink" title="2.Positioning (or Anchors)"></a>2.Positioning (or Anchors)</h3><ul><li><code>^</code> : The ^ (circumflex or caret) <strong>when not used inside square brackets</strong> means look only at the beginning of the target string.</li><li><code>$</code> : The $ (dollar) means look only at the end of the target string.</li><li><code>.</code> : The . (period) means any character(s) in this position.</li></ul><h3 id="3-Iteration-‘metacharacters’"><a href="#3-Iteration-‘metacharacters’" class="headerlink" title="3.Iteration ‘metacharacters’"></a>3.Iteration ‘metacharacters’</h3><p>The following is a set of <strong>iteration metacharacters</strong> that can control the <strong>number of times</strong> the <strong>preceding</strong> character is found in our searches.</p><ul><li><code>?</code> : The ? (question mark) matches when the preceding character occurs 0 or 1 times only.</li><li><code>*</code>: The * (asterisk or star) matches when the preceding character occurs 0 or more times.</li><li><code>+</code> : The + (plus) matches when the preceding character occurs 1 or more times.</li><li><code>{n}</code> : Matches when the preceding character, or character range, occurs n times exactly.</li><li><code>{n,m}</code> : Matches when the preceding character occurs at least n times but not more than m times.</li><li><code>{n, }</code> : Matches when the preceding character occurs at least n times.</li></ul><h3 id="4-More-‘metacharacters’"><a href="#4-More-‘metacharacters’" class="headerlink" title="4.More ‘metacharacters’"></a>4.More ‘metacharacters’</h3><ul><li><code>()</code> : The ( (open parenthesis) and ) (close parenthesis) may be used to group (or bind) parts of our search expression together. Officially this is called a subexpression and subexpressions may be nested to any depth.</li><li><code>|</code>: The | (vertical bar or pipe) is called alternation in techspeak and means find the left hand OR right values.</li></ul><h3 id="5-Common-Extensions-and-Abbreviations"><a href="#5-Common-Extensions-and-Abbreviations" class="headerlink" title="5.Common Extensions and Abbreviations"></a>5.Common Extensions and Abbreviations</h3><ul><li><code>\d</code> : Match any character in the range 0 - 9.</li><li><code>\D</code> : Match any character NOT in the range 0 - 9.</li><li><code>\s</code> : Match any whitespace characters (space, tab etc.).</li><li><code>\S</code> : Match any character NOT whitespace (space, tab).</li><li><code>\w</code> : Match any character in the range 0 - 9, A - Z, a - z and punctuation.</li><li><code>\W</code> : Match any character NOT the range 0 - 9, A - Z, a - z and punctuation.</li><li><code>\b</code> : Word boundary. Match any character(s) at the beginning <code>(\bxx)</code> and/or end <code>(xx\b)</code> of a word.</li><li><code>\B</code> : Not word boundary. Match any character(s) NOT at the beginning <code>(\bxx)</code> and/or end <code>(xx\b)</code> of a word.</li></ul><p><strong>PS</strong>: Punctuation symbols: <code>. , " ' ? ! ; : # $ % & ( ) * + - / < > = @ [ ] \ ^ _ { } | ~</code></p><h3 id="6-Some-pragmatic-gists-for-checking-URL-validation-in-Objective-C"><a href="#6-Some-pragmatic-gists-for-checking-URL-validation-in-Objective-C" class="headerlink" title="6.Some pragmatic gists for checking URL validation in Objective-C"></a>6.Some pragmatic gists for checking URL validation in Objective-C</h3><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// IP address check.</span></div><div class="line">- (<span class="built_in">BOOL</span>)isIPAddress {</div><div class="line"> <span class="built_in">NSString</span> *result = <span class="keyword">self</span>;</div><div class="line"> <span class="keyword">if</span> ([<span class="keyword">self</span> containsProtocol]) {</div><div class="line"> result = [result componentsSeparatedByString:<span class="string">@"//"</span>].lastObject;</div><div class="line"> }</div><div class="line"> <span class="built_in">NSRegularExpression</span> *regex = [[<span class="built_in">NSRegularExpression</span> alloc] initWithPattern:<span class="string">@"[a-zA-Z]"</span> options:<span class="number">0</span> error:<span class="literal">NULL</span>];</div><div class="line"> <span class="built_in">NSInteger</span> matches = [regex numberOfMatchesInString:result options:<span class="number">0</span> range:<span class="built_in">NSMakeRange</span>(<span class="number">0</span>, result.length)];</div><div class="line"> <span class="keyword">return</span> (matches <= <span class="number">0</span>);</div><div class="line">}</div></pre></td></tr></table></figure><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// IP address validation check.</span></div><div class="line">- (<span class="built_in">BOOL</span>)isValidIPAddress {</div><div class="line"> <span class="built_in">NSString</span> *result = <span class="keyword">self</span>;</div><div class="line"> <span class="keyword">if</span> ([<span class="keyword">self</span> containsProtocol]) {</div><div class="line"> result = [result componentsSeparatedByString:<span class="string">@"//"</span>].lastObject;</div><div class="line"> }</div><div class="line"> <span class="built_in">NSString</span> *ipRegEx = <span class="string">@"(\\d{1,3}\\.){3}(\\d){1,3}(:\\d{1,})?"</span>;</div><div class="line"> <span class="built_in">NSPredicate</span> *ipTest = [<span class="built_in">NSPredicate</span> predicateWithFormat:<span class="string">@"SELF MATCHES %@"</span>, ipRegEx];</div><div class="line"> <span class="keyword">return</span> [ipTest evaluateWithObject:result];</div><div class="line">}</div></pre></td></tr></table></figure><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// DNS address validation check.</span></div><div class="line">- (<span class="built_in">BOOL</span>)isValidDNSAddress {</div><div class="line"> <span class="built_in">NSString</span> *result = <span class="keyword">self</span>;</div><div class="line"> <span class="keyword">if</span> (![<span class="keyword">self</span> containsProtocol]) {</div><div class="line"> result = [<span class="built_in">NSString</span> stringWithFormat:<span class="string">@"http://%@"</span>,<span class="keyword">self</span>];</div><div class="line"> }</div><div class="line"> <span class="built_in">NSString</span> *urlRegEx =</div><div class="line"> <span class="string">@"(http|https)://((\\w)*|([0-9]*)|([-|_])*)+([\\.|/]((\\w)*|([0-9]*)|([-|_])*))+(:[0-9]{1,})?"</span>;</div><div class="line"> <span class="built_in">NSPredicate</span> *urlTest = [<span class="built_in">NSPredicate</span> predicateWithFormat:<span class="string">@"SELF MATCHES %@"</span>, urlRegEx];</div><div class="line"> <span class="keyword">return</span> [urlTest evaluateWithObject:result];</div><div class="line">}</div></pre></td></tr></table></figure><p>For more details and test case, please check <a href="https://gist.github.com/CaliosD/9e4262d0151764f3a07b32dc2ce90eff" target="_blank" rel="external">here</a>.</p><h3 id="Ref"><a href="#Ref" class="headerlink" title="Ref"></a>Ref</h3><ul><li><a href="http://www.zytrax.com/tech/web/regex.htm" target="_blank" rel="external">http://www.zytrax.com/tech/web/regex.htm</a></li></ul>]]></content>
<summary type="html">
<h3 id="Some-definitions-before-we-start"><a href="#Some-definitions-before-we-start" class="headerlink" title="Some definitions before we start:"></a>Some definitions before we start:</h3><ul>
<li><strong>literal</strong>: A <strong>literal</strong> is any character we use in a search or matching expression, for example, to find <strong>ind</strong> in w <strong>ind</strong> ows the <strong>ind</strong> is a <strong>literal</strong> string - each character plays a part in the search, it is <strong>literally</strong> the string we want to find.</li>
</ul>
</summary>
<category term="iOS" scheme="http://www.caliosd.gq/tags/iOS/"/>
</entry>
<entry>
<title>CAS登录之iOS端总结</title>
<link href="http://www.caliosd.gq/2017/04/12/summary-for-CAS-login-in-iOS/"/>
<id>http://www.caliosd.gq/2017/04/12/summary-for-CAS-login-in-iOS/</id>
<published>2017-04-12T06:41:09.000Z</published>
<updated>2018-09-15T08:47:27.000Z</updated>
<content type="html"><![CDATA[<p>CAS(集中式认证服务,Central Authentication Service)登录在后台开发中使用很广泛,它可以允许一个用户访问多个web app,而只需要提供一次凭证(如用户名和密码)。不仅方便用户跨程序使用系统,也实现了认证和web app分类,提高了安全性。</p><p>目前在移动端(本文以iOS为例)涉及到CAS登录的项目主要有两种实现方式:CAS模拟登录和RESTful方式的登录。现总结如下,供以后备查。</p><a id="more"></a><h3 id="1-CAS模拟登录"><a href="#1-CAS模拟登录" class="headerlink" title="1.CAS模拟登录"></a>1.CAS模拟登录</h3><p>这种方式相当于模拟登录web端,在请求回web app的登录页面后,返回CAS登录页面,从中获取lt和execution,请求CAS登录接口时,带上service=BaseURL/web-name/shiro-cas,通过这个操作判断是否登录,如果登录,就在web server中种入session,使得之后移动端调用web server的接口可以生效。</p><h5 id="1-1-使用的项目"><a href="#1-1-使用的项目" class="headerlink" title="1.1 使用的项目"></a>1.1 使用的项目</h5><p>CAS不被公司拥有的、无法提供Restful方式的项目接口。</p><h5 id="1-2-登录流程示意图"><a href="#1-2-登录流程示意图" class="headerlink" title="1.2 登录流程示意图"></a>1.2 登录流程示意图</h5><p><img src="http://7xkwcv.com1.z0.glb.clouddn.com/CAS%E6%A8%A1%E6%8B%9F%E7%99%BB%E5%BD%95%E6%B5%81%E7%A8%8B%E7%A4%BA%E6%84%8F%E5%9B%BE-%E8%84%B1%E6%95%8F.jpg" alt="CAS模拟登录示意图"></p><p>注:</p><ul><li>Step1为GET,Step2、3为POST。</li><li>Step2的j_captcha_response即为验证码。</li><li>请求验证码一步非必需。</li></ul><h3 id="2-RESTful方式"><a href="#2-RESTful方式" class="headerlink" title="2.RESTful方式"></a>2.RESTful方式</h3><p>可以使用Github上的开源demo(<a href="https://github.com/acu-dev/objc-cas-client)实现,也可以直接使用AFNetworking实现。本质上流程完全相同。" target="_blank" rel="external">https://github.com/acu-dev/objc-cas-client)实现,也可以直接使用AFNetworking实现。本质上流程完全相同。</a></p><h5 id="2-1-登录流程示意图"><a href="#2-1-登录流程示意图" class="headerlink" title="2.1 登录流程示意图"></a>2.1 登录流程示意图</h5><p><img src="http://7xkwcv.com1.z0.glb.clouddn.com/CAS%E7%99%BB%E5%BD%95RESTful%E6%B5%81%E7%A8%8B%E7%A4%BA%E6%84%8F%E5%9B%BE-%E8%84%B1%E6%95%8F.jpg" alt="RESTful方式"></p><p>注:</p><ul><li>Step1、2、3均为POST。</li></ul><h5 id="2-2-技术细节"><a href="#2-2-技术细节" class="headerlink" title="2.2 技术细节"></a>2.2 技术细节</h5><ul><li>Step1:请求TGT时 server路径形如:<a href="http://xxx.xxx.xxx.xxx/cas/v1/tickets/" target="_blank" rel="external">http://xxx.xxx.xxx.xxx/cas/v1/tickets/</a> 。需要传递账号密码参数给CAS,形如: account=somebody&password=password。TGT请求成功时响应状态码为201,并且包含Location字段,形如:Location: <a href="http://www.whatever.com/cas/v1/tickets/{TGT" target="_blank" rel="external">http://www.whatever.com/cas/v1/tickets/{TGT</a> id},然后截取TGT并保存。</li><li>Step2:根据TGT 请求ST server路径形如:<a href="http://xxx.xxx.xxx.xxx//cas/v1/tickets/TGT-xxxxxxxxx。参数形如:service=http://localhost:8080/web-name,成功后保存返回的ST。" target="_blank" rel="external">http://xxx.xxx.xxx.xxx//cas/v1/tickets/TGT-xxxxxxxxx。参数形如:service=http://localhost:8080/web-name,成功后保存返回的ST。</a></li><li>Step3:调用WebServer登录接口需要将获得的ST作为参数传递,形如:cas-service-ticket=ST-xxxxxxxxx.</li><li>登出CAS请求(DELETE)形如:<a href="http://xxx.xxx.xxx.xxx/cas/v1/tickets/TGT-xxxxxxxxx。然后向WebServer发起POST登出请求(即调用接口" target="_blank" rel="external">http://xxx.xxx.xxx.xxx/cas/v1/tickets/TGT-xxxxxxxxx。然后向WebServer发起POST登出请求(即调用接口</a> /web-name/logout) 成功后判定为整个登出流程完成。</li></ul><h3 id="3-参考资料:"><a href="#3-参考资料:" class="headerlink" title="3.参考资料:"></a>3.参考资料:</h3><ul><li>CAS原理介绍:<a href="http://p.primeton.com/articles/53c64e25e13823319f000068" target="_blank" rel="external">http://p.primeton.com/articles/53c64e25e13823319f000068</a></li><li>CAS认证的说明:<a href="https://www.purdue.edu/apps/account/html/cas_presentation_20110407.pdf" target="_blank" rel="external">https://www.purdue.edu/apps/account/html/cas_presentation_20110407.pdf</a></li><li>iOS开源Demo:<a href="https://github.com/acu-dev/objc-cas-client" target="_blank" rel="external">https://github.com/acu-dev/objc-cas-client</a></li><li>Jasig CAS RESTful API:<a href="https://wiki.jasig.org/display/CASUM/RESTful+API" target="_blank" rel="external">https://wiki.jasig.org/display/CASUM/RESTful+API</a></li><li>REST Protocol:<a href="https://apereo.github.io/cas/4.2.x/protocol/REST-Protocol.html" target="_blank" rel="external">https://apereo.github.io/cas/4.2.x/protocol/REST-Protocol.html</a></li></ul>]]></content>
<summary type="html">
<p>CAS(集中式认证服务,Central Authentication Service)登录在后台开发中使用很广泛,它可以允许一个用户访问多个web app,而只需要提供一次凭证(如用户名和密码)。不仅方便用户跨程序使用系统,也实现了认证和web app分类,提高了安全性。</p>
<p>目前在移动端(本文以iOS为例)涉及到CAS登录的项目主要有两种实现方式:CAS模拟登录和RESTful方式的登录。现总结如下,供以后备查。</p>
</summary>
<category term="iOS" scheme="http://www.caliosd.gq/tags/iOS/"/>
</entry>
<entry>
<title>GPS and related</title>
<link href="http://www.caliosd.gq/2017/03/22/gps-and-related/"/>
<id>http://www.caliosd.gq/2017/03/22/gps-and-related/</id>
<published>2017-03-22T06:35:02.000Z</published>
<updated>2018-09-15T08:20:14.000Z</updated>
<content type="html"><![CDATA[<h3 id="iPhone-Location-How-apps-access-the-iPhone-location-and-what-they-can-do-with-it"><a href="#iPhone-Location-How-apps-access-the-iPhone-location-and-what-they-can-do-with-it" class="headerlink" title="iPhone Location: How apps access the iPhone location and what they can do with it."></a>iPhone Location: How apps access the iPhone location and what they can do with it.</h3><ul><li>Requesting an appropriate level of accuracy(the options are 3km, 1km, 100m, 10m, Best, or Best for navigation) is all about minimizing battery drain.<a id="more"></a><ul><li>At a low of accuracy(e.g. 3km), the phone could determine its location solely from cellular or wifi signals, which would avoid powering up the GPS and conserve battery life.</li><li>At higher levels of accuracy(e.g. 10m), the phone listens to all available signals that it can use to determine its location — which includes GPS and iBeacons, in addition to wifi and cellular</li><li>Apple does not say specifically say which signals are used at which levels of accuracy.</li></ul></li><li>An app can force the use of GPS by specifying “Best” — this will cause the phone to use GPS constantly to determine location as accurately as possible.</li><li>When the phone has a lock on 4 satellites, it can determine its elevation.<ul><li>With 3 satellites, it can only determine location.</li></ul></li><li>Significant Location Changes Only<ul><li>As an alternative to receiving updates every time the phone moves, an app can request that it receive a location update only when the phone’s location has changed “significantly”.</li><li>This will avoid powering up the GPS and will rely on cell towers and wifi signals only — resulting in substantially reduced power consumption.</li><li>“Significant” is not defined.</li></ul></li><li>Region Monitoring<ul><li>where an app defines one or more regions by specifying their center and radius.</li><li>Then the phone notifies the app whenever it enters or exits a region.</li><li>This uses wifi and cell towers only, so accuracy is similar to “significant changes only” and regions will only work if they are fairly large.</li></ul></li><li>iBeacons<ul><li>iBeacons are small wireless sensors that can transmit data to an iPhone using Bluetooth Low Energy(BLE)</li><li>iBeacons allow apps to receive special promotions, coupons, recommendations, etc, from business in real time when users are nearby</li><li>An app can display the information received from the business, or it can make use of the knowledge that the iBeacon is nearby for navigational purposes as a form of region monitoring.</li></ul></li><li>Inaccuracy Makes Distances Long<ul><li>When tracking the user’s path with high level of accuracy, any inaccuracies in location measurement will almost always cause the path to be longer than it should.</li></ul></li><li>“Snapping” to Roads<ul><li>To counteract the extra distance problem and provide a smoother path, iPhone will “snap” location updates to a known road network when the user is traveling about 15mph(on the assumption that the user is driving or biking during that time).</li><li>Apps have no way of knowing when this is happening — all they see are location updates like normal, but when the locations are later projected on a map, it is clear when this was happening.</li></ul></li><li>GPS Signal Quality<ul><li>GPS requires line-of-sight communication, so anything that blocks a direct path from the phone to a satellite can interfere with the signal (this includes trees, mountains, buildings, car roofs, etc)</li><li>When a direct GPS signal is blocked and the user is near a large solid object like a building, the GPS signal can arrive at the phone indirectly after bouncing off the building.</li><li>Because GPS works by measuring very precisely how long the signal takes to get from the satellite to the phone, when the signal takes a longer path like this, the calculated location will be off.</li></ul></li><li>Post-Processing<ul><li>It is difficult to overcome these limitations on the phone while the app is running.</li><li>However, if the app is recording a user’s path and saving it to a server(e.g. the way a fitness tracking app would), the server can make some corrections after upload, such as<ul><li>Snapping to roads or other known locations</li><li>Smoothing out jagged parts of the path</li><li>Replacing the inaccurate elevation reported by the phone with known elevation at that location</li></ul></li></ul></li><li>Map Displays<ul><li>iPhone makes displaying maps in apps really easy</li><li>Many common functions are built-in and require very little from the app developer, including:<ul><li>Displaying user location on the map</li><li>Displaying annotations and overlays on the map that zoom and scale with the map</li><li>Centering the map on the user’s location</li></ul></li><li>Geocoding(getting latitude/longitude coordinates from an address) and reverse geocoding(getting an address from latitude/longitude coordinates) is also built-in and quite easy to use</li></ul></li><li><strong>How iPhone determines its location:</strong><ul><li>Four types of signals<ul><li>iPhone can use 4 different types of signals to determine its location<ul><li>Cellular</li><li>Wifi</li><li>GPS</li><li>Bluetooth (from iBeacons)</li></ul></li><li>The phone’s location is determined by combining one or more of these signals(Which signals are used depends on availability and the requested accuracy)</li><li>All of this happens behind the scenes — any app can access the location of the phone, but the app does not know how it was calculated</li></ul></li><li>Cellular<ul><li>The location of cell towers is known to a high degree of accuracy. Based on the signal strength from various towers, the phone can estimate how far it is from each of them and calculate the phone’s location</li><li>This is always available when the phone has cell coverage, but is not very accurate since the towers can be far away and distance based on signal strength is not very accurate</li></ul></li><li>Wifi<ul><li>The phone uses the same process to determine location using wifi signals as it does for cellular signals, with distance estimated to the wifi hotspots based on signal strength</li><li>The locations of wifi hotspots have been crowdsourced for this purpose</li><li>Using wifi is more accurate than cell towers, but the user needs to have wifi turned on</li></ul></li><li>iBeacons<ul><li>When the phone detects a Bluetooth Low Energy signal from an iBeacon, in addition to knowing that an iBeacon is nearby, the phone can use the relative signal strength from multiple iBeacons to determine its location using the same technique that it uses for cellular and wifi signals</li><li>iBeacon signals only travel about 50 feet, so this location would be fairly accurate</li><li>This is particularly useful indoors, where GPS reception is poor</li></ul></li><li>GPS<ul><li>GPS is the most accurate of the 4 signals, but it is slow to get started</li><li>The phone needs to “lock on” 3-4 satellites within its line of sight before it can use GPS to determine location<ul><li>This can take anywhere from 15 seconds to several minutes, depending on the phone’s view of the sky</li><li>3 satellites can provide location; a fourth provides elevation</li><li>The phone uses its location calculated from other sources(e.g. cellular and wifi) to determine which satellites are within its line of sight at the current time, speeding up this process(this is called Assisted GPS, or A-GPS)</li></ul></li><li>Before the phone has a GPS lock, location is less accurate</li></ul></li></ul></li></ul><h3 id="In-iOS8-is-there-any-way-to-turn-off-all-radios-EXCEPT-GPS"><a href="#In-iOS8-is-there-any-way-to-turn-off-all-radios-EXCEPT-GPS" class="headerlink" title="In iOS8 is there any way to turn off all radios EXCEPT GPS?"></a>In iOS8 is there any way to turn off all radios EXCEPT GPS?</h3><p>(Latest answer on Aug 13, 2016. <a href="https://discussions.apple.com/thread/6543352?start=15&tstart=0" target="_blank" rel="external">https://discussions.apple.com/thread/6543352?start=15&tstart=0</a>)<br>iOS 9 has enabled GPS in Airplane mode. Tried and tested, works as it should.<br>iOS 8 walk-around would be setting sim-card pin, restarting the phone, not entering pin#, sim would be deactivated, however the phone would still look for emergency bands.<br>iOS 7 had a bug that would activate GPS when compass app was launched while the phone was in Airplane mode.</p><p>Since iOS 8.2, you can still use the GPS even in flight mode.(<a href="http://apple.stackexchange.com/questions/98649/does-airplane-mode-disable-gps?rq=1" target="_blank" rel="external">http://apple.stackexchange.com/questions/98649/does-airplane-mode-disable-gps?rq=1</a>)<br>GPS continues to work in airplane mode. Tested on iPhone 5s and SE with iOS 8, 9 and now 10. It may take longer (up to several minutes)to get the first fix but has full performance afterwards. It even works in an airplane when the iPhone is close to a window.</p><hr><h3 id="Ref"><a href="#Ref" class="headerlink" title="Ref:"></a>Ref:</h3><ul><li><a href="https://learnaboutcoding.wordpress.com/iphone-course/iphone-location/" target="_blank" rel="external">https://learnaboutcoding.wordpress.com/iphone-course/iphone-location/</a></li></ul>]]></content>
<summary type="html">
<h3 id="iPhone-Location-How-apps-access-the-iPhone-location-and-what-they-can-do-with-it"><a href="#iPhone-Location-How-apps-access-the-iPhone-location-and-what-they-can-do-with-it" class="headerlink" title="iPhone Location: How apps access the iPhone location and what they can do with it."></a>iPhone Location: How apps access the iPhone location and what they can do with it.</h3><ul>
<li>Requesting an appropriate level of accuracy(the options are 3km, 1km, 100m, 10m, Best, or Best for navigation) is all about minimizing battery drain.
</summary>
<category term="iOS" scheme="http://www.caliosd.gq/tags/iOS/"/>
</entry>
<entry>
<title>Objective-C Runtime Programming Guide 翻译及详解</title>
<link href="http://www.caliosd.gq/2017/03/16/Objective-C-runtime-programming-guide-translation/"/>
<id>http://www.caliosd.gq/2017/03/16/Objective-C-runtime-programming-guide-translation/</id>
<published>2017-03-16T08:35:30.000Z</published>
<updated>2018-09-15T08:14:57.000Z</updated>
<content type="html"><![CDATA[<p>近日大脑一抽筋,尝试使用GitBook Editor的同时翻了一篇Runtime Programming的官方文档。</p><p>链接在此:<a href="https://caliosd.gitbooks.io/objective-c-runtime-programming-guide/" target="_blank" rel="external">https://caliosd.gitbooks.io/objective-c-runtime-programming-guide/</a></p><p>以此留念。欢迎各种批评指正。</p>]]></content>
<summary type="html">
<p>近日大脑一抽筋,尝试使用GitBook Editor的同时翻了一篇Runtime Programming的官方文档。</p>
<p>链接在此:<a href="https://caliosd.gitbooks.io/objective-c-runtime-programmi
</summary>
<category term="iOS" scheme="http://www.caliosd.gq/tags/iOS/"/>
</entry>
<entry>
<title>[SourceCodeRead]-MJExtension</title>
<link href="http://www.caliosd.gq/2017/03/07/[SourceCodeRead]-MJExtension/"/>
<id>http://www.caliosd.gq/2017/03/07/[SourceCodeRead]-MJExtension/</id>
<published>2017-03-07T09:09:51.000Z</published>
<updated>2018-09-15T08:29:04.000Z</updated>
<content type="html"><![CDATA[<p>MJExtension的源码,从最常用的<code>-objectArrayWithKeyValuesArray:</code>(字典数组转模型数组)和<code>-keyValuesArrayWithObjectArray:</code>(模型数组转字典数组)入手,就进入了<code>NSObject+MJKeyValue</code>分类。在梳理过这个类的实现方法之后,发现归根到底,核心的方法就这两个:<code>-setKeyValue:error:</code>(字典转模型)和<code>-keyValuesWithError:</code>(模型转字典)。</p><p>我们分别来看这两个方法。</p><h3 id="setKeyValue-error"><a href="#setKeyValue-error" class="headerlink" title="-setKeyValue:error:"></a><code>-setKeyValue:error:</code></h3><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">- (<span class="keyword">instancetype</span>)setKeyValues:(<span class="built_in">NSDictionary</span> *)keyValues error:(<span class="built_in">NSError</span> *__autoreleasing *)error</div><div class="line">{</div><div class="line"> MJAssertError([keyValues isKindOfClass:[<span class="built_in">NSDictionary</span> <span class="keyword">class</span>]], <span class="keyword">self</span>, error, <span class="string">@"keyValues参数不是一个字典"</span>);</div><div class="line"> …</div><div class="line">}</div></pre></td></tr></table></figure><p>进入函数后,首先是一个断言,保证传入参数是一个字典。这里体现了防御式编程的思想,具体介绍可以看<a href="https://en.wikipedia.org/wiki/Defensive_programming" target="_blank" rel="external">wiki</a>(英文版比中文版的信息完整)。</p><figure class="highlight crystal"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">NSArray *ignoredPropertyNames = <span class="literal">nil</span>;</div><div class="line"><span class="keyword">if</span> ([[<span class="keyword">self</span> <span class="class"><span class="keyword">class</span>] <span class="title">respondsToSelector</span>:@<span class="title">selector</span>(<span class="title">ignoredPropertyNames</span>)]) {</span></div><div class="line"> ignoredPropertyNames = [[<span class="keyword">self</span> <span class="class"><span class="keyword">class</span>] <span class="title">ignoredPropertyNames</span>];</span></div><div class="line">}</div></pre></td></tr></table></figure><p>首先检查是否有需要忽略的属性,即不进行字典和模型转换的属性。<code>ignoredPropertyNames</code>这个方法来自<code>MJKeyValue</code>协议,除此之外,<code>MJKeyValue</code>协议中还定义了一些对于属性名的特殊处理,如替换属性名(<code>replacedKeyFromPropertyName</code>),指定数组中需要转换的模型类(<code>objectClassInArray</code>),和字典转模型完毕/模型转字典完毕时调用的方法。</p><p>往下走,进入核心部分。<br><figure class="highlight clojure"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">[[self class] enumerateIvarsWithBlock:^(<span class="name">MJIvar</span> *ivar, BOOL *stop) { </div><div class="line"> ...</div><div class="line">}</div></pre></td></tr></table></figure></p><p>其中<code>enumerateIvarsWithBlock:</code>实现如下:<br><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div></pre></td><td class="code"><pre><div class="line">+ (<span class="keyword">void</span>)enumerateIvarsWithBlock:(MJIvarsBlock)block</div><div class="line">{</div><div class="line"> <span class="keyword">static</span> <span class="keyword">const</span> <span class="keyword">char</span> MJCachedIvarsKey;</div><div class="line"> <span class="comment">// 获得成员变量</span></div><div class="line"> <span class="built_in">NSMutableArray</span> *cachedIvars = objc_getAssociatedObject(<span class="keyword">self</span>, &MJCachedIvarsKey); <span class="comment">// A</span></div><div class="line"> <span class="keyword">if</span> (cachedIvars == <span class="literal">nil</span>) {</div><div class="line"> cachedIvars = [<span class="built_in">NSMutableArray</span> array];</div><div class="line"></div><div class="line"> [<span class="keyword">self</span> enumerateClassesWithBlock:^(__<span class="keyword">unsafe_unretained</span> Class c, <span class="built_in">BOOL</span> *stop) {</div><div class="line"> <span class="comment">// 1.获得所有的成员变量</span></div><div class="line"> <span class="keyword">unsigned</span> <span class="keyword">int</span> outCount = <span class="number">0</span>;</div><div class="line"> Ivar *ivars = class_copyIvarList(c, &outCount); <span class="comment">// B</span></div><div class="line"></div><div class="line"> <span class="comment">// 2.遍历每一个成员变量</span></div><div class="line"> <span class="keyword">for</span> (<span class="keyword">unsigned</span> <span class="keyword">int</span> i = <span class="number">0</span>; i<outCount; i++) {</div><div class="line"> MJIvar *ivar = [MJIvar cachedIvarWithIvar:ivars[i]]; <span class="comment">// C</span></div><div class="line"> ivar.srcClass = c;</div><div class="line"> <span class="built_in">NSString</span> *key = [<span class="keyword">self</span> ivarKey:ivar.propertyName];</div><div class="line"> [ivar setKey:key forClass:<span class="keyword">self</span>];</div><div class="line"> <span class="comment">// 数组中的模型类</span></div><div class="line"> [ivar setObjectClassInArray:[<span class="keyword">self</span> ivarObjectClassInArray:ivar.propertyName] forClass:<span class="keyword">self</span>];</div><div class="line"> [cachedIvars addObject:ivar]; <span class="comment">// D</span></div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="comment">// 3.释放内存</span></div><div class="line"> free(ivars); <span class="comment">// B</span></div><div class="line"> }];</div><div class="line"> objc_setAssociatedObject(<span class="keyword">self</span>, &MJCachedIvarsKey, cachedIvars, OBJC_ASSOCIATION_RETAIN_NONATOMIC); <span class="comment">// A</span></div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="comment">// 遍历成员变量</span></div><div class="line"> <span class="built_in">BOOL</span> stop = <span class="literal">NO</span>;</div><div class="line"> <span class="keyword">for</span> (MJIvar *ivar <span class="keyword">in</span> cachedIvars) {</div><div class="line"> block(ivar, &stop);</div><div class="line"> <span class="keyword">if</span> (stop) <span class="keyword">break</span>;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></p><p>有这样三个点可以看一下:<br>A:<code>id objc_getAssociatedObject(id object, const void *key)</code>和<code>void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)</code>这一对方法,没什么需要赘述的。<br>B:<code>Ivar *class_copyIvarList(Class cls, unsigned int *outCount)</code>经常用来获取类的成员变量列表,返回值是一个指针的数组,类型是Ivar,在使用过之后记得到调用<code>free()</code>来释放数组的内存。</p><blockquote><p>我在这里简单写了一个获取类的成员变量列表的方法,仅供参考。<br><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line">+ (<span class="keyword">void</span>)printIvarsOfClass:(Class)c</div><div class="line">{</div><div class="line"> <span class="keyword">unsigned</span> <span class="keyword">int</span> ivarCount = <span class="number">0</span>;</div><div class="line"> Ivar *ivars = class_copyIvarList(c, &ivarCount);</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"====== Ivars of %@ are: "</span>, c);</div><div class="line"></div><div class="line"> <span class="keyword">for</span> (<span class="keyword">unsigned</span> <span class="keyword">int</span> i = <span class="number">0</span>; i < ivarCount; i++) {</div><div class="line"> Ivar var = ivars[i];</div><div class="line"> <span class="keyword">const</span> <span class="keyword">char</span>* name = ivar_getName(var);</div><div class="line"> <span class="keyword">const</span> <span class="keyword">char</span>* typeEncoding = ivar_getTypeEncoding(var);</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"%s, %s \n"</span>, name, typeEncoding);</div><div class="line"> }</div><div class="line"> free(ivars);</div><div class="line">}</div></pre></td></tr></table></figure></p></blockquote><p>C:用Ivar初始化一个<code>MJIvar</code>实例。MJIvar可以理解为在原有的Ivar基础上,添加了成员名、成员属性名、类型等常用属性和常用方法。<br>D:将设置好的MJIvar实例添加到缓存的成员变量中,以备后用,同时也提高了后续使用的效率。</p><p>后面的代码,作者注释已经很清楚了,就不再赘述。</p><h3 id="keyValuesWithError"><a href="#keyValuesWithError" class="headerlink" title="-keyValuesWithError:"></a><code>-keyValuesWithError:</code></h3><p>来看这个核心方法:<br><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line">- (<span class="built_in">NSDictionary</span> *)keyValuesWithError:(<span class="built_in">NSError</span> *__autoreleasing *)error</div><div class="line">{</div><div class="line"> <span class="comment">// 如果自己不是模型类</span></div><div class="line"> <span class="keyword">if</span> ([MJFoundation isClassFromFoundation:[<span class="keyword">self</span> <span class="keyword">class</span>]]) <span class="keyword">return</span> (<span class="built_in">NSDictionary</span> *)<span class="keyword">self</span>;</div><div class="line"> ...</div><div class="line">}</div></pre></td></tr></table></figure></p><p>首先涉及到<code>MJFoundation</code>类的一个(也是唯一一个)方法:<code>+isClassFromFoundation:</code>。代码并不复杂:<br><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div></pre></td><td class="code"><pre><div class="line">+ (<span class="keyword">void</span>)load</div><div class="line">{</div><div class="line"> _foundationClasses = [<span class="built_in">NSSet</span> setWithObjects:</div><div class="line"> [<span class="built_in">NSObject</span> <span class="keyword">class</span>],</div><div class="line"> [<span class="built_in">NSURL</span> <span class="keyword">class</span>],</div><div class="line"> [<span class="built_in">NSDate</span> <span class="keyword">class</span>],</div><div class="line"> [<span class="built_in">NSNumber</span> <span class="keyword">class</span>],</div><div class="line"> [<span class="built_in">NSDecimalNumber</span> <span class="keyword">class</span>],</div><div class="line"> [<span class="built_in">NSData</span> <span class="keyword">class</span>],</div><div class="line"> [<span class="built_in">NSMutableData</span> <span class="keyword">class</span>],</div><div class="line"> [<span class="built_in">NSArray</span> <span class="keyword">class</span>],</div><div class="line"> [<span class="built_in">NSMutableArray</span> <span class="keyword">class</span>],</div><div class="line"> [<span class="built_in">NSDictionary</span> <span class="keyword">class</span>],</div><div class="line"> [<span class="built_in">NSMutableDictionary</span> <span class="keyword">class</span>],</div><div class="line"> [<span class="built_in">NSString</span> <span class="keyword">class</span>],</div><div class="line"> [<span class="built_in">NSMutableString</span> <span class="keyword">class</span>], <span class="literal">nil</span>];</div><div class="line">}</div><div class="line"></div><div class="line">+ (<span class="built_in">BOOL</span>)isClassFromFoundation:(Class)c</div><div class="line">{</div><div class="line"> <span class="keyword">return</span> [_foundationClasses containsObject:c];</div><div class="line">}</div></pre></td></tr></table></figure></p><p>只是检查一下传入的类是否属于基本类。这里要看的,其实是它上面的方法<code>+(void)load</code>。</p><blockquote><p><code>+load</code>在所属类一加载完的时候就会被调用,非常早。如果是在应用中或是应用链接的框架中实现<code>+load</code>,<code>+load</code>会在<code>main()</code>函数调用之前执行。如果是在一个可加载的bundle中实现<code>+load</code>,它就会在bundle加载的过程中执行。<code>+load</code>特别的一点是,如果在类和该类的category中都实现了<code>+load</code>方法,两个<code>+load</code>方法都会被执行。这就意味着<code>+load</code>是存放类似method swizzling这种魔鬼的好地方。:]</p><p>与<code>+load</code>类似的是<code>+initialize</code>方法,它是个比<code>+load</code>更安全的存放代码的地方。当类第一次加载的时候,<code>+initialize</code>并不会调用。当有消息发送给类的 时候,runtime会首先检查<code>+initialize</code>是否已经被调用,如果没有,就会在处理消息发送之前先调用<code>+initialize</code>。</p></blockquote><p>更多关于<code>+load</code>和<code>+initialize</code>的讲解,可以看Mike Ash的<a href="https://www.mikeash.com/pyblog/friday-qa-2009-05-22-objective-c-class-loading-and-initialization.html" target="_blank" rel="external">这篇</a>。</p>]]></content>
<summary type="html">
<p>MJExtension的源码,从最常用的<code>-objectArrayWithKeyValuesArray:</code>(字典数组转模型数组)和<code>-keyValuesArrayWithObjectArray:</code>(模型数组转字典数组)入手,就进入
</summary>
<category term="iOS" scheme="http://www.caliosd.gq/tags/iOS/"/>
<category term="SourceCodeRead" scheme="http://www.caliosd.gq/tags/SourceCodeRead/"/>
</entry>
<entry>
<title>C的宏魔法</title>
<link href="http://www.caliosd.gq/2017/02/23/black-magic-in-C/"/>
<id>http://www.caliosd.gq/2017/02/23/black-magic-in-C/</id>
<published>2017-02-23T03:14:18.000Z</published>
<updated>2018-09-15T08:30:43.000Z</updated>
<content type="html"><![CDATA[<h2 id="“-”和”-”"><a href="#“-”和”-”" class="headerlink" title="“#”和”##”"></a>“#”和”##”</h2><ul><li><strong>#:String-izing Tokens</strong>,作用是把宏参数转化成以传入的参数名为内容的字符串。通常可以用于debug时打印参数。<br>比如说,如果定义成这样:<figure class="highlight lisp"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">#define PRINT_TOKEN(<span class="name">token</span>) NSLog(<span class="name">#token</span> <span class="string">" is %d"</span>, token)</div></pre></td></tr></table></figure></li></ul><p>使用时,<code>PRINT_TOKEN(1+2);</code>替换之后就会变为<code>NSLog(1+2 " is %d", token);</code>得到<code>1+2 is 3</code>。</p><p>如果不使用#,就会变成:<br><figure class="highlight less"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="selector-id">#define</span> <span class="selector-tag">PRINT_TOKEN</span>(token) <span class="selector-tag">NSLog</span>(<span class="string">"token"</span> <span class="string">" is %d"</span>, token)</div></pre></td></tr></table></figure></p><p>这里我们期望<code>" "</code>的作用是变量替换,而在实际语言中,双引号有字符串拼接的作用。为了遵守单一职责的原则,#的存在是有意义的。</p><ul><li><strong>##:Pasting Tokens</strong>,在把宏参数拼接到一起、形成一个新的参数时非常有用。比如我们要通过一个变量,给另一个相关名字的变量赋值:</li></ul><figure class="highlight excel"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">#define DECLARE_AND_SET(<span class="built_in">type</span>, varname, <span class="built_in">value</span>) <span class="built_in">type</span> varname = <span class="built_in">value</span>; <span class="built_in">type</span> orig_##varname = varname;</div><div class="line"></div><div class="line">DECLARE_AND_SET( <span class="built_in">int</span>, area, <span class="number">12</span> );</div></pre></td></tr></table></figure><p>在这个例子中,如果不使用“##”,就不能这样一步到位地实现粘贴成 <strong>变量</strong> 的目的。如果宏中的参数是类名,就需要先拼接字符串,再使用<code>NSClassFromString()</code>这样的方法将字符串转换成<code>Class</code>类型。这在实际项目中还是很有用的。</p><h2 id="metamacro-concat-宏"><a href="#metamacro-concat-宏" class="headerlink" title="metamacro_concat_宏"></a><code>metamacro_concat_</code>宏</h2><p>在<code>ReactiveCocoa</code>的代码中看到过这样两个宏定义:</p><figure class="highlight mipsasm"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="comment">#define metamacro_concat(A, B) \</span></div><div class="line"> metamacro_concat_(A, <span class="keyword">B)</span></div><div class="line"><span class="comment">#define metamacro_concat_(A, B) A ## B</span></div></pre></td></tr></table></figure><p><code>metamacro_concat_</code>这个宏其实和直接使用<code>##</code>语法基本等效,那么为什么不直接使用<code>##</code>写到使用它的宏里呢?</p><p>首先来理解下宏处理过程的操作流程图,可以想象成有一个指针,从前到后地查找宏和宏参数:</p><p><img src="http://7xkwcv.com1.z0.glb.clouddn.com/NestedMacroHandling.jpg" alt="处理过程"></p><p>在宏计算的过程中出现多层嵌套时,都是遇到<code>##</code>就立即进行拼接。<code>metamacro_concat_</code>的作用相当于在<code>##</code>外面包了一层,降低了<code>##</code>的在宏处理时的“优先级”。</p><p>因此,同样是计算<code>metamacro_concat(1, metamacro_concat(2, 3))</code>,直接使用<code>##</code>和间接使用的推导过程就截然不同,具体推导如下。</p><figure class="highlight excel"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div></pre></td><td class="code"><pre><div class="line">// Test-<span class="number">1</span>:直接使用`##`</div><div class="line">#define metamacro_concat(A,B) A ## B</div><div class="line"></div><div class="line">metamacro_concat(<span class="number">1</span>, metamacro_concat(<span class="number">2</span>, <span class="number">3</span>))</div><div class="line">// 判断metamacro_concat中是否有#和##,有##,实参替换形参</div><div class="line">=> <span class="number">1</span> ## metamacro_concat(<span class="number">2</span>,<span class="number">3</span>)</div><div class="line">=> <span class="number">1</span>metamacro_concat(<span class="number">2</span>,<span class="number">3</span>)</div><div class="line">// 没有<span class="number">1</span>metamacro_concat的定义,报错。</div><div class="line"></div><div class="line"></div><div class="line">// Test-<span class="number">2</span>:间接使用`##`</div><div class="line">#define metamacro_concat(A,B) _metamacro_concat(A,B)</div><div class="line">#define _metamacro_concat(A,B) A ## B</div><div class="line"></div><div class="line">metamacro_concat(<span class="number">1</span>, metamacro_concat(<span class="number">2</span>,<span class="number">3</span>))</div><div class="line">// 判断metamacro_concat中是否有##,没有,替换成_metamacro_concat</div><div class="line">=> _metamacro_concat(<span class="number">1</span>, metamacro_concat(<span class="number">2</span>,<span class="number">3</span>))</div><div class="line">=> _metamacro_concat(<span class="number">1</span>, _metamacro_concat(<span class="number">2</span>,<span class="number">3</span>))</div><div class="line">=> _metamacro_concat(<span class="number">1</span>, <span class="number">2</span> ## <span class="number">3</span>)</div><div class="line">=> _metamacro_concat(<span class="number">1</span>, <span class="number">23</span>)</div><div class="line">=> <span class="number">1</span> ## <span class="number">23</span></div><div class="line">=> <span class="number">123</span></div></pre></td></tr></table></figure><hr><p><strong>Ref: </strong></p><ul><li><a href="https://onevcat.com/2014/01/black-magic-in-macro/" target="_blank" rel="external">https://onevcat.com/2014/01/black-magic-in-macro/</a></li><li><a href="http://stackoverflow.com/a/3776901/1594792" target="_blank" rel="external">http://stackoverflow.com/a/3776901/1594792</a></li><li><a href="http://www.cprogramming.com/tutorial/cpreprocessor.html" target="_blank" rel="external">http://www.cprogramming.com/tutorial/cpreprocessor.html</a></li></ul>]]></content>
<summary type="html">
<h2 id="“-”和”-”"><a href="#“-”和”-”" class="headerlink" title="“#”和”##”"></a>“#”和”##”</h2><ul>
<li><strong>#:String-izing Tokens</strong>,作用是
</summary>
</entry>
<entry>
<title>__attribute__小结</title>
<link href="http://www.caliosd.gq/2017/02/20/summary-for-attribute/"/>
<id>http://www.caliosd.gq/2017/02/20/summary-for-attribute/</id>
<published>2017-02-20T07:44:45.000Z</published>
<updated>2018-09-15T08:08:48.000Z</updated>
<content type="html"><![CDATA[<h3 id="1-attribute-是什么?"><a href="#1-attribute-是什么?" class="headerlink" title="1.__attribute__是什么?"></a>1.<code>__attribute__</code>是什么?</h3><p><code>__attribute__</code>是Clang提供的、用来在C,C++和Objective-C中修饰代码定义的 <strong>编译指令</strong>。它为声明的代码提供了额外的属性,来帮助编译器优化或者为代码的使用者显示有用的警告信息。</p><h3 id="2-attribute-有什么用?"><a href="#2-attribute-有什么用?" class="headerlink" title="2.__attribute__有什么用?"></a>2.<code>__attribute__</code>有什么用?</h3><p><code>__attribute__</code>命令提供代码运行需要的上下文。提供上下文的重要性怎么强调都不过分。通过详细地给出API如何命令编译器的定义,开发者可以获得显而易见的益处。同样是一束鲜花,是要在情人节送给女朋友还是要用来探望术后的病人,效果是完全不同的。而<code>__attribute__</code>的作用就好像鲜花上附加的一张卡片,上面写的是“送给最爱的某某”还是“愿某某早日康复”是完全由你来决定的。</p><p>正如Mattt在<a href="http://nshipster.com/__attribute__/" target="_blank" rel="external">这里</a>指出的:</p><blockquote><p>当涉及到编译器优化时,上下文就是王道。通过约束你的代码的解释方式,你可以让生成的代码尽可能的高效。不只是为了编译器。下一个看代码的人也会感激(你所提供的)额外的上下文信息。</p></blockquote><h3 id="3-attribute-怎么用?"><a href="#3-attribute-怎么用?" class="headerlink" title="3.__attribute__怎么用?"></a>3.<code>__attribute__</code>怎么用?</h3><p>它的语法是这样的:<code>__attribute__((interrupt(“TYPE")))</code>。</p><p>每当你有机会来给代码定义(变量,参数,函数,方法,类等等)提供额外的上下文信息时,你都应该使用<code>__attribute__</code>。但除非你知道自己在干什么,否则,不要滥用。因为提供一个错误的上下文比没有提供上下文更糟糕。</p><h3 id="4-attribute-使用举例?"><a href="#4-attribute-使用举例?" class="headerlink" title="4.__attribute__使用举例?"></a>4.<code>__attribute__</code>使用举例?</h3><p><strong>声明一个API在某平台的可用性:<strong>attribute</strong>((availability(…))):如 NS_AVAILABLE 和 NS_DEPRECATED</strong></p><figure class="highlight lisp"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">#define NS_AVAILABLE(<span class="name">_mac</span>, _ios) CF_AVAILABLE(<span class="name">_mac</span>, _ios)</div><div class="line">#define CF_AVAILABLE(<span class="name">_mac</span>, _ios) __attribute__((<span class="name">availability</span>(<span class="name">macosx</span>,introduced=_mac)))</div></pre></td></tr></table></figure><p>在<code>Foundation</code>库的<code>NSString.h</code>中的使用:<br><figure class="highlight erlang"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">- <span class="params">(BOOL)</span>containsString:<span class="params">(NSString *)</span>str NS_AVAILABLE<span class="params">(<span class="number">10</span>_10, <span class="number">8</span>_0)</span>;</div></pre></td></tr></table></figure></p><figure class="highlight lisp"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">#define NS_DEPRECATED(<span class="name">_macIntro</span>, _macDep, _iosIntro, _iosDep, ...) CF_DEPRECATED(<span class="name">_macIntro</span>, _macDep, _iosIntro, _iosDep, __VA_ARGS__)</div><div class="line">#define CF_DEPRECATED(<span class="name">_macIntro</span>, _macDep, _iosIntro, _iosDep, ...) __attribute__((<span class="name">availability</span>(<span class="name">macosx</span>,introduced=_macIntro,deprecated=_macDep,message=<span class="string">""</span> __VA_ARGS__)))</div></pre></td></tr></table></figure><p>同样在<code>Foundation</code>库的<code>NSString.h</code>中:<br><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">- (<span class="keyword">nullable</span> <span class="keyword">id</span>)initWithContentsOfFile:(<span class="built_in">NSString</span> *)path <span class="built_in">NS_DEPRECATED</span>(<span class="number">10</span>_0, <span class="number">10</span>_4, <span class="number">2</span>_0, <span class="number">2</span>_0);(用带有encoding/usedEncoding和error参数的方法替代)</div></pre></td></tr></table></figure></p><p><strong>声明一个函数中包含带有指定格式参数的格式化字符串:<strong>attribute</strong>((format(…))):NS_FORMAT_FUNCTION</strong></p><p>在<code>Foundation</code>库的<code>NSObjCRuntime.h</code>中:<br><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">FOUNDATION_EXPORT <span class="keyword">void</span> <span class="built_in">NSLog</span>(<span class="built_in">NSString</span> *format, ...) <span class="built_in">NS_FORMAT_FUNCTION</span>(<span class="number">1</span>,<span class="number">2</span>) <span class="built_in">NS_NO_TAIL_CALL</span>;</div><div class="line"><span class="meta">#define NS_FORMAT_FUNCTION(F,A) __attribute__((format(__NSString__, F, A)))</span></div></pre></td></tr></table></figure></p><p>语法说明:<code>__attribute__((format(format_type, format_string_index, first_format_argument_index)))format_type: one of printf, scant, strftime, strfmon or __NSString__</code></p><p><strong>声明被修饰的函数必须在重写的时候调用super的方法:<strong>attribute</strong>((objc_requires_super)):NS_REQUIRES_SUPER</strong></p><p>在<code>Foundation</code>库的<code>NSObjcRuntime.h</code>中:<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="meta">#</span><span class="bash">ifndef NS_REQUIRES_SUPER // 防止头文件的重复包含和编译。</span></div><div class="line"><span class="meta">#</span><span class="bash"><span class="keyword">if</span> __has_attribute(objc_requires_super)</span></div><div class="line"><span class="meta">#</span><span class="bash">define NS_REQUIRES_SUPER __attribute__((objc_requires_super))</span></div><div class="line"><span class="meta">#</span><span class="bash"><span class="keyword">else</span></span></div><div class="line"><span class="meta">#</span><span class="bash">define NS_REQUIRES_SUPER</span></div><div class="line"><span class="meta">#</span><span class="bash">endif</span></div><div class="line"><span class="meta">#</span><span class="bash">endif</span></div></pre></td></tr></table></figure></p><p>比如在自定义的<code>@interface Student: NSObject</code>中声明了:<br><figure class="highlight lisp"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">- (<span class="name">void</span>)foo NS_REQUIRES_SUPER<span class="comment">;</span></div></pre></td></tr></table></figure></p><p>那么在<code>@interface CollegeStudent: Student</code>的实现文件中,像这样写一个空的<code>-foo</code>函数是会报出警告的:<br><figure class="highlight oxygene"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">- (void)foo <span class="comment">{</span></div><div class="line">}</div><div class="line"></div><div class="line">warning: <span class="function"><span class="keyword">Method</span> <span class="title">possibly</span> <span class="title">missing</span> <span class="title">a</span> [<span class="title">super</span> <span class="title">foo</span>] <span class="title">call</span></span></div></pre></td></tr></table></figure></p><p><strong>其他更多的attribute使用:</strong></p><p><img src="http://7xkwcv.com1.z0.glb.clouddn.com/Attributes%20in%20Clang%20%E2%80%94%20Clang%203.8%20documentation.png" alt="Attributes in Clange"></p><h3 id="5-小结"><a href="#5-小结" class="headerlink" title="5.小结"></a>5.小结</h3><p>总而言之,当你想给自己写的类、方法、参数添加一些限制条件的时候,<code>__attribute__</code>可以为你提供一种可行的解决方案。</p><hr><p><strong>Ref:</strong></p><ul><li><a href="https://blog.twitter.com/2014/attribute-directives-in-objective-c" target="_blank" rel="external">https://blog.twitter.com/2014/attribute-directives-in-objective-c</a></li><li><a href="http://releases.llvm.org/3.8.0/tools/clang/docs/AttributeReference.html" target="_blank" rel="external">http://releases.llvm.org/3.8.0/tools/clang/docs/AttributeReference.html</a></li><li><a href="http://nshipster.com/__attribute__/" target="_blank" rel="external">http://nshipster.com/__attribute__/</a></li><li><a href="http://blog.sunnyxx.com/2016/05/14/clang-attributes/" target="_blank" rel="external">http://blog.sunnyxx.com/2016/05/14/clang-attributes/</a></li></ul>]]></content>
<summary type="html">
<h3 id="1-attribute-是什么?"><a href="#1-attribute-是什么?" class="headerlink" title="1.__attribute__是什么?"></a>1.<code>__attribute__</code>是什么?</h
</summary>
<category term="iOS" scheme="http://www.caliosd.gq/tags/iOS/"/>
</entry>
<entry>
<title>Objective-C基础教程(第2版)总结</title>
<link href="http://www.caliosd.gq/2017/02/17/summary-for-Objective-C-foundation-book/"/>
<id>http://www.caliosd.gq/2017/02/17/summary-for-Objective-C-foundation-book/</id>
<published>2017-02-17T07:50:18.000Z</published>
<updated>2018-09-15T08:52:49.000Z</updated>
<content type="html"><![CDATA[<p>— <strong>Updated: 170729</strong> —</p><p>软件设计的准则:(SOLID)</p><ul><li>单一职责:Single responsibility</li><li>开放封闭:Open-closed</li><li>里氏代换:Liskov substitution</li><li>接口分离:Interface segregation</li><li>依赖倒置:Dependency inversion</li></ul><p>设计模式是内功心法,而所谓23种只是表现形式罢了。</p><hr><ul><li>OOP真正的革命性就是它在调用代码中使用间接(indirect),如使用变量、文件和参数等。“没有什么是通过间接解决不了的问题,如果有,就再加一层。”<blockquote><p>比如黑魔法第二课中提到的<code>#define _CONCAT(A,B) A ## B</code>的作用。再比如,键值编码(KVC)也是一种间接更改对象状态的方式。</p></blockquote></li><li>过程式编程以函数为中心,面向对象编程以数据为中心。</li><li>类是一种结构,它表示对象的类型;对象是一种结构,它包含值和指向其类的隐藏指针。</li><li>在Objective-C中只要看到@符号,你都可以把它看成是C语言的扩展。如:NSLog(@“this is a log: %@“, log);</li><li>对象是带有代码的C struct。因此,id实际上是一个指针,指向其中的某个结构。</li><li>@interface告诉编译器这个类的对象的数据成员(即对象的C struct应该是什么样子)和它提供的特性。</li><li>Objective-C的中缀符(infix notation)。</li><li>Objective-C中并没有真正意义上的私有方法,也无法通过标记为私有方法而禁止其他代码调用它。这是Objective-C动态本质的副作用。</li></ul><blockquote><p>这里可以和runtime联系起来。因为即便在编译器编译后,代码也可以在运行时改变类所拥有的属性、或是改变方法调用的实现。这给开发者提供了很多便利,也不可避免地带来一些副作用(side effect)。</p></blockquote><ul><li>在Objective-C中每个方法调用都获得了一个名为self的隐藏参数,它是一个指向接收消息的对象的指针。方法使用self参数查找它们要使用的实例变量。eg: self->fillColor = c;</li><li>由于对象的局部变量特定于该对象的实例,因此称为实例变量,通常简写为ivars。</li><li>Objective-C有个极好的特性,你可以把类当做对象来向类发送消息。</li></ul><blockquote><p><del>这个“极好的特性”,是相对于谁来说的?C?其他语言??</del><br>应该是指可以有类方法,而不仅是实例方法。</p></blockquote><ul><li>多态使得来自不同类的对象可以定义共享相同名称的方法。动态类型能使程序直到执行时才确定对象所属的类。动态绑定则能使程序直到执行时才确定要对对象调用的实际方法。而id类型是这三者的基础。</li><li>防御式编程。</li><li>Cocoa的方法名称如果以get开头,表明我们提供的是一个指针,而指针所指向的空间则是用来存储该方法生成的数据。如<code>NSValue</code>的<code>getValue:</code>方法。</li></ul><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="built_in">NSValue</span> *value = [<span class="built_in">NSValue</span> valueWithBytes:&rect objCType:<span class="keyword">@encode</span>(<span class="built_in">CGRect</span>)];</div><div class="line"><span class="built_in">NSMutableArray</span> *arr = [<span class="built_in">NSMutableArray</span> array];</div><div class="line">[arr addObject:value];</div><div class="line"></div><div class="line"><span class="built_in">NSValue</span> *v = arr[<span class="number">0</span>];</div><div class="line">[v getValue:&rect];</div></pre></td></tr></table></figure><ul><li><code>#import</code>时,带尖括号的语句是用来导入系统头文件的,是只读的;而带引号的语句说明导入的是项目本地的头文件,是可编辑的。</li><li>self:<ul><li><code>self</code> is a special variable in Objective-C, inside an instance method this variable refers to the receiver(object) of the message that invoked the method, while in a class method <code>self</code> will indicate which class is calling.</li><li><code>self</code> refers to the actual object that is executing the current method, it is an invisible argument passed automatically by the runtime environment to your instance methods.</li></ul></li><li>对象的初始化过程:来自NSObject的类方法alloc,为该类(对象?)分配一块足够大的内存,以存放该类的全部实例变量,并将其清零;实例方法init用于获得一个对象并使其运行。</li><li>为什么要写成:<code>Car *car = [[Car alloc] init]; 而不是 Car *car = [Car alloc]; [car init];</code>? (关键词:类簇)<ul><li>因为初始化方法返回的对象可能与分配的对象不同。如NSNumber、NSString和NSArray这样的类簇的情况。</li><li>由于init方法可以接受参数,所以该方法的代码能够检查其接受的参数,并断定返回另一个类的对象可能更合适。</li><li>Xcode会检查是否alloc与init是嵌套的,如果不是会给出提示。<code>*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -objCType only defined for abstract class. Define -[NSPlaceholderNumber objCType]!’</code></li></ul></li></ul><figure class="highlight mipsasm"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">NSNumber *<span class="keyword">boo </span>= [[NSNumber alloc] initWithBool:NO]<span class="comment">;</span></div><div class="line">NSNumber *<span class="keyword">boo1 </span>= [NSNumber alloc]<span class="comment">;</span></div><div class="line">NSNumber *<span class="keyword">boo2 </span>= [<span class="keyword">boo1 </span>initWithBool:NO]<span class="comment">;</span></div><div class="line">NSLog(@<span class="string">"boo: %@ \n boo1: %@ \n boo2: %@"</span>,[<span class="keyword">boo </span>class],[<span class="keyword">boo1 </span>class],[<span class="keyword">boo2 </span>class])<span class="comment">;</span></div></pre></td></tr></table></figure><p>这段代码打印的的结果是:<br><figure class="highlight armasm"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">boo: </span>__NSCFBoolean</div><div class="line"><span class="keyword">boo1: </span>NSPlaceholderNumber</div><div class="line"><span class="keyword">boo2: </span>__NSCFBoolean</div></pre></td></tr></table></figure></p><p>可见,仅仅分配内存的对象boo1和初始化方法返回的boo2并不相同。</p><ul><li>通常,接受参数最多的初始化方法最终成为 <strong>指定初始化函数</strong>。</li><li>预编译指令<code>@property</code>可以通知编译器:“这个对象具有这个类型的这个名称的这种属性”,还可以传递一些关于property(如<code>readonly</code>和<code>readwrite</code>)和对象的内存管理(<code>retain</code>,<code>assign</code>或<code>copy</code>)的信息;<code>@synthesize</code>预编译指令可以通知编译器生成访问器方法。</li><li>有的属性列表文件,特别是首选项文件,是以压缩的二进制格式存储的。通过使用plutil命令:<code>plutil -convert xml1 文件名.plist</code>,可以将这些文件转换成人可以理解的字面形式。</li><li>对于KVC,Cocoa会自动装箱和开箱标量值。也就是说,当使用 <code>setValueForKey</code>时,它自动将标量值(int、float和struct)放入<code>NSNumber</code>或<code>NSValue</code>中;当使用<code>-setValueForKey:</code>时,它自动将标量值从这些对象中取出。仅KVC具有这种自动装箱功能,常规方法调用和属性语法不具备该功能。</li><li><p><code>valueForKey:</code>在Objective-C运行时中使用元数据打开对象并进入其中查找需要的信息。在C或C++语言中不能执行这种操作。通过使用KVC,没有相关getter方法也能获取对象值,不需要通过对象指针来直接访问实例变量。</p><blockquote><p>这里体现的就是Objective-C“自省”的特性吧?</p></blockquote></li><li><p>(在console的打印结果中)注意<null>与(null)之间的区别。<null>是一种<code>[NSNull null]</code>对象,而(null)是一个真正的<code>nil</code>值。</null></null></p></li><li><code>NSPredicate</code>字符串中也可以用<code>%K</code>来指定键路径。如<code>predicate = [NSPredicate predicateWithFormat: @"%K beginswith %@", @"name", @"B"];</code>。</li><li>谓词机制不进行静类型检查。</li><li>在编写谓词字符串时,尽量使用[cd]修饰符。其中,c表示“不区分大小写(case insensitive)”,d表示“不区分发音符号(diacritic insensitive)”,[cd]表示“既不区分大小写,也不区分发音符号”。如:<code>predicate = [NSPredicate predicateWithFormat: @"name BEGINSWITH[cd] 'HERB'"];</code>。</li></ul>]]></content>
<summary type="html">
<p>— <strong>Updated: 170729</strong> —</p>
<p>软件设计的准则:(SOLID)</p>
<ul>
<li>单一职责:Single responsibility</li>
<li>开放封闭:Open-closed</li>
<li>里氏
</summary>
<category term="iOS" scheme="http://www.caliosd.gq/tags/iOS/"/>
</entry>
</feed>