-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
546 lines (405 loc) · 57.8 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>刘哈笔记</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta property="og:type" content="website">
<meta property="og:title" content="刘哈笔记">
<meta property="og:url" content="http://www.liuxiaoming.com/index.html">
<meta property="og:site_name" content="刘哈笔记">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="刘哈笔记">
<link rel="alternate" href="/atom.xml" title="刘哈笔记" type="application/atom+xml">
<link rel="icon" href="/favicon.png">
<link href="//fonts.googleapis.com/css?family=Source+Code+Pro" rel="stylesheet" type="text/css">
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<div id="container">
<div id="wrap">
<header id="header">
<div id="banner"></div>
<div id="header-outer" class="outer">
<div id="header-title" class="inner">
<h1 id="logo-wrap">
<a href="/" id="logo">刘哈笔记</a>
</h1>
<h2 id="subtitle-wrap">
<a href="/" id="subtitle">把时间浪费在最有价值的事情上</a>
</h2>
</div>
<div id="header-inner" class="inner">
<nav id="main-nav">
<a id="main-nav-toggle" class="nav-icon"></a>
<a class="main-nav-link" href="/">Home</a>
<a class="main-nav-link" href="/archives">Archives</a>
</nav>
<nav id="sub-nav">
<a id="nav-rss-link" class="nav-icon" href="/atom.xml" title="RSS Feed"></a>
<a id="nav-search-btn" class="nav-icon" title="Search"></a>
</nav>
<div id="search-form-wrap">
<form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form"><input type="search" name="q" class="search-form-input" placeholder="Search"><button type="submit" class="search-form-submit"></button><input type="hidden" name="sitesearch" value="http://www.liuxiaoming.com"></form>
</div>
</div>
</div>
</header>
<div class="outer">
<section id="main">
<article id="post-source-application-open" class="article article-type-post" itemscope itemprop="blogPost">
<div class="article-meta">
<a href="/2017/10/11/source-application-open/" class="article-date">
<time datetime="2017-10-11T14:47:07.000Z" itemprop="datePublished">2017-10-11</time>
</a>
</div>
<div class="article-inner">
<div class="article-entry" itemprop="articleBody">
<h1 id="iOS如何识别App打开的来源"><a href="#iOS如何识别App打开的来源" class="headerlink" title="iOS如何识别App打开的来源"></a>iOS如何识别App打开的来源</h1><h2 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h2><p>在数据统计方面层面看App打开来源非常重要,特别是对于分享以及付费引流的衡量效果上有着非常关键的作用。</p>
<p>iOS App最常用的打开途径有三种:</p>
<ol>
<li>消息(本地、远程)推送打开</li>
<li>scheme跳转打开</li>
<li>UniversalLinks打开</li>
</ol>
<h2 id="如何区分"><a href="#如何区分" class="headerlink" title="如何区分"></a>如何区分</h2><p>App启动后标志着App底层已经准备完备的地方就有可以用来区分的标志,也就是在AppDelegate中的<code>application:didFinishLaunchingWithOptions:</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></pre></td><td class="code"><pre><div class="line">if (launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]) {</div><div class="line"> NSLog(@"远程推送打开");</div><div class="line">} else if (launchOptions[UIApplicationLaunchOptionsLocalNotificationKey]) {</div><div class="line"> NSLog(@"本地推送打开");</div><div class="line">} else if (launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey]) {</div><div class="line"> NSLog(@"UniversalLinks打开");</div><div class="line">} else if (launchOptions[UIApplicationLaunchOptionsURLKey]) {</div><div class="line"> NSLog(@"Scheme跳转打开");</div><div class="line">} else if (!launchOptions) {</div><div class="line"> NSLog(@"手动点击打开");</div><div class="line">}</div></pre></td></tr></table></figure>
<h2 id="再多说一些"><a href="#再多说一些" class="headerlink" title="再多说一些"></a>再多说一些</h2><p>launchOptions除了用来区分App的开发方式,还承载着打开时的一些数据,比如scheme跳转、UniversalLinks打开的时候的一些具体链接,之前应用的bundleID等数据方便追述。</p>
<p>例如UniversalLinks中,我们就可以通过如下方法获得链接,而不一定要等到专用的Delegate方法返回给我们</p>
<figure class="highlight plain"><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">NSUserActivity *act = [[launchOptions objectForKey:UIApplicationLaunchOptionsUserActivityDictionaryKey] objectForKey:@"UIApplicationLaunchOptionsUserActivityKey"];</div><div class="line">NSString *url = [act.webpageURL absoluteString];</div></pre></td></tr></table></figure>
<h2 id="其他枚举的意义"><a href="#其他枚举的意义" class="headerlink" title="其他枚举的意义"></a>其他枚举的意义</h2><ul>
<li><p>UIApplicationLaunchOptionsURLKey<br> 在scheme跳转打开的时候出现,用于获取scheme地址的key;如”enalibaba://home”</p>
</li>
<li><p>UIApplicationLaunchOptionsSourceApplicationKey<br> 从其他App跳转打开的时候出现 值为一个字符串表示来源App的BundleID;如:”com.apple.mobilesafari” 表示从Safari跳转</p>
</li>
<li><p>UIApplicationLaunchOptionsRemoteNotificationKey<br> 从远程推送打开,这个key对应的值是一个Dictionary,里面就是推送的Payload</p>
</li>
<li><p>UIApplicationLaunchOptionsLocalNotificationKey<br> 从本地推送打开,这个key对应的值是一个Dictionary,里面就是推送的Payload</p>
</li>
<li><p>UIApplicationLaunchOptionsAnnotationKey<br> 这个Key应该不会再看见了,它只能通过 <code>application:openURL:sourceApplication:annotation:</code> 打开App的时候才会出现,但这个方法已经被标记为 Deprecated 从9.0之后不再支持。</p>
</li>
<li><p>UIApplicationLaunchOptionsLocationKey<br> 基于地理位置触发的App打开,官方文档应该更新过了,已经找不到原文,大意是如果App开启了地理位置,在App退出到后台之后,如果触发了地理位置打开App,那么LaunchOptions就会有这个Key,可用来开启地理位置事件监听的标志,但这种地理位置触发打开App的能力需要App Store审核才能开启使用。</p>
</li>
<li><p>UIApplicationLaunchOptionsNewsstandDownloadsKey<br> 关于杂志更新的,用到极少,不多描述</p>
</li>
<li><p>UIApplicationLaunchOptionsBluetoothCentralsKey<br> 蓝牙服务提供设备唤醒App时出现的Key,数据为数组,代表设备列表</p>
</li>
<li><p>UIApplicationLaunchOptionsBluetoothPeripheralsKey<br> 蓝牙被服务设备唤醒App时出现的Key,数据为数组,代表设备列表</p>
</li>
<li><p>UIApplicationLaunchOptionsShortcutItemKey<br> 从3D Touch打开App时出现此Key</p>
</li>
<li><p>UIApplicationLaunchOptionsUserActivityDictionaryKey<br> UniversalLinks打开时出现此Key,用于获得继续打开行为的一些数据,通常是点击的链接</p>
</li>
<li><p>UIApplicationLaunchOptionsUserActivityTypeKey<br> UniversalLinks打开时出现此Key,值为NSUserActivityTypeBrowsingWeb,而且这个枚举值就这个以</p>
</li>
<li><p>UIApplicationLaunchOptionsCloudKitShareMetadataKey<br> A key indicating that the app received a CloudKit share invitation. 相关文档比较少,推测是从文件里分享给App打开时会出现此Key。</p>
</li>
</ul>
<p>参考<br><a href="https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623073-application?language=objc" target="_blank" rel="external">https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623073-application?language=objc</a><br><a href="http://nshipster.cn/launch-options/" target="_blank" rel="external">http://nshipster.cn/launch-options/</a><br><a href="http://www.jianshu.com/p/2ab2716c334e" target="_blank" rel="external">http://www.jianshu.com/p/2ab2716c334e</a><br><a href="http://www.jianshu.com/p/6a1eb76ec776" target="_blank" rel="external">http://www.jianshu.com/p/6a1eb76ec776</a></p>
</div>
<footer class="article-footer">
<a data-url="http://www.liuxiaoming.com/2017/10/11/source-application-open/" data-id="cj8n5h1z20001dkckt4ahcuya" class="article-share-link">Share</a>
</footer>
</div>
</article>
<article id="post-ios-universal-links-2" class="article article-type-post" itemscope itemprop="blogPost">
<div class="article-meta">
<a href="/2017/09/10/ios-universal-links-2/" class="article-date">
<time datetime="2017-09-10T06:07:20.000Z" itemprop="datePublished">2017-09-10</time>
</a>
</div>
<div class="article-inner">
<header class="article-header">
<h1 itemprop="name">
<a class="article-title" href="/2017/09/10/ios-universal-links-2/">iOS App间相互跳转漫谈 - part2</a>
</h1>
</header>
<div class="article-entry" itemprop="articleBody">
<h2 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h2><p><a href="http://www.liuxiaoming.com/2017/09/07/ios-universal-links/">上一篇</a>已经介绍了iOS系统层面提供的应用之间跳转的技术和实施方案,本篇会带大家更深入的了解UniveralLinks技术,探究其绑定运作原理,使用时的技巧。</p>
<h2 id="运作原理"><a href="#运作原理" class="headerlink" title="运作原理"></a>运作原理</h2><p>对于UniversalLinks的生效和绑定原理,官方文档并未说明,通过亲自尝试整理出了其绑定原理,这有助于对于触发时机把握:</p>
<ol>
<li><p>App从iOS9开始,安装App成功后,系统会检查App是否对UniversalLinks技术做了支持,如果检查到有做支持,则从App的associated-domains配置里读取绑定域名。</p>
</li>
<li><p>访问该域名下预设的JSON文件<code>apple-app-site-association</code>,读取JSON文件中的app绑定信息,与当前App内的信息做对比。</p>
</li>
<li><p>如果匹配成功,在系统本地内部就做完了双向绑定。</p>
</li>
</ol>
<p>也就是只要App安装完成,就可以即刻触发UniversalLinks打开App啦!不需要像推送那样,需要打开一次。</p>
<h2 id="巧用UniveralLinks判断App是否安装"><a href="#巧用UniveralLinks判断App是否安装" class="headerlink" title="巧用UniveralLinks判断App是否安装"></a>巧用UniveralLinks判断App是否安装</h2><p>上一篇文章也提到了,目前所有包括UniversalLinks技术都无法直接对App的安装做判断。</p>
<p>但我要说:非也!UniversalLinks提供的一项特性可以让我们反向推断App是否安装,而且准确率相当高,不像scheme跳转那样,只能做延时。</p>
<p>UniversalLinks在触发时,也就是链接被点击时,系统会与本地已经建立双向绑定的数据进行匹配,匹配到的话,这个网络请求就会在系统层面被拦截,系统就会转而打开绑定的App,并把完整URL传给App的openURL的Delegate方法来处理。反之,如果App没有安装,那么点击链接时系统无法匹配到信息,则就将请求放行,让服务端接收到这个请求来处理。</p>
<p>看到这里已经很明了了吧,用户只要点击了,服务端可以通过是否接收到请求来判断App是否已安装。</p>
<p>当然这里也会不准,但概率比较小,原因如果用户是长按打开,或者打开App后点击了右上角的系统的返回按钮后,系统会下次触发UniversalLinks的时候就不会拦截请求了,导致接收到请求的设备其实已经App,而我们无法知道。要想重新让系统拦截,用户需要点击的时候长按link,并且选择通过App打开,之后才又会被拦截。</p>
<p>但已经比scheme跳转设置一个timeout要来的靠谱的多了。</p>
<h2 id="巧用UniveralLinks拉新引旧"><a href="#巧用UniveralLinks拉新引旧" class="headerlink" title="巧用UniveralLinks拉新引旧"></a>巧用UniveralLinks拉新引旧</h2><p>UniversalLinks作为一个平滑使用体验的工具类技术来说,本身不具备拉新客户的功能:比如新用户如果从站外点击UniversalLinks,那么用户没有装App的话,只是会访问m站而已,无它;引导老用户从H5迁移到App的能力也比较弱,一直使用m站的用户,只有在页面顶部看到有一个入口,点击才能打开App。原因是用户如果直接输入了m站的地址,在站内访问,是不会触发UniversalLinks的,只是会在绑定的H5页面顶部有一个bar提醒用户,点击可以跳转到App的相同功能,可以说是很弱的。</p>
<p>让我们看看怎么才能让它具备强制拉新引旧能力:</p>
<ul>
<li>首先要具备判断是否安装App的条件来区分新用户还是老用户,如果未安装则为新,已安装则为旧。方法上面一节已经介绍。</li>
<li>接下来服务端开发一个特殊的UniversalLinks,类似于<a href="https://app.alibaba.com/babalink,对app做好双向绑定,注意,这里域名与m站的m.alibaba.com域名必须不同。" target="_blank" rel="external">https://app.alibaba.com/babalink,对app做好双向绑定,注意,这里域名与m站的m.alibaba.com域名必须不同。</a></li>
<li>babalink接收两种参数scheme=<scheme url="">&url=<http url="">,前者是应用的scheme跳转,服务端拼接传入打开App后需要打开页面的scheme值,这个值是给已安装App的情况用的;还有url中传入需要跳转的http页面,通常是这个链接原本功能的m站链接。</http></scheme></li>
<li>这里举个栗子,例如跳转到产品详情,app的scheme是enalibaba://detail?id=123;而m站的页面是<a href="https://m.alibaba.com/product?id=123;那么服务端在搜索结果页每个结果的链接应该是`https://app.alibaba.com/babalink?scheme=enalibaba://detail?id=123&url=https://m.alibaba.com/product?id=123`每个一级参数下的值都需要urlencode,这边为了查看方便就不做了。" target="_blank" rel="external">https://m.alibaba.com/product?id=123;那么服务端在搜索结果页每个结果的链接应该是`https://app.alibaba.com/babalink?scheme=enalibaba://detail?id=123&url=https://m.alibaba.com/product?id=123`每个一级参数下的值都需要urlencode,这边为了查看方便就不做了。</a></li>
<li>这样,用户到了m站搜索结果页的时候,每个搜索结果点击的都是上面这种链接,这种链接的域名与m站本身域名不同,在App已安装的情况下就会触发系统拦截请求,跳转App,App在打开时通过完整url中的scheme参数,进行页面跳转即可完成引导老用户的操作。</li>
<li>再看看新用户,新用户因为没有安装App,所以这个link的请求会被发送到服务端,服务端根据情况控制将其引导到AppStore去安装App,或者直接重定向到url参数的网页。后者不用说还是延续用户在m站的上的继续浏览,前者打开了AppStore安装了App后,用户打开时首页怎么办?</li>
<li>这里这里可以利用归因系统来做到从AppStore打开,还能继续之前的操作,大致原理是服务端接收到link的请求,从请求中将这个请求转化为用户指纹存起来。</li>
<li>当用户打开刚安装好的App后,App会在启动的时候将一些可作为指纹的数据当做参数通过某个接口传给服务端,服务端接收App的指纹数据参数后,通过算法匹配之前的用户指纹,匹配上后将用户点击的完整链接返回给客户端,由客户端来讲链接解析,利用其scheme值进行跳转,即可完美continue用户的行为。</li>
</ul>
<h2 id="从其他App直接打开"><a href="#从其他App直接打开" class="headerlink" title="从其他App直接打开"></a>从其他App直接打开</h2><ul>
<li>从浏览器打开,只要是当前页面的域名和用户点击按钮的universallinks的域名是不同的,就会触发。</li>
<li>在App中打开会有些麻烦,只有通过<code>[[UIApplication sharedApplication] opentURL:@"https://app.alibaba.com/babalink?scheme=enalibaba://detail?id=123&url=https://m.alibaba.com/product?id=123"]</code>这样打开,才能跳转到目标app,如果把链接塞到webview中则不会触发,请求一定会到服务端。</li>
<li>所以,微信里直接通过聊天内容发送链接跳转是做不到的。</li>
<li>这里可以让链接检测浏览器UA来判断链接是否为从其他App打开,如果是,则重定向到<code>https://app-c.alibaba.com/babalink?scheme=enalibaba://detail?id=123&url=https://m.alibaba.com/product?id=123</code>注意这个链接的域名不是<code>app.alibaba.com</code>,这个h5页面展示一个按钮,跳转链接为<code>https://app.alibaba.com/babalink?scheme=enalibaba://detail?id=123&url=https://m.alibaba.com/product?id=123</code>。为什么?因为要跨域,而且点击行为在浏览器中进行是无法阻断的。</li>
<li>此时用户点击中间页按钮时,即便在其他App,不管是Facebook还是微信,都可以触发UniversalLink啦!</li>
</ul>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>总结一下,巧用UniversalLink需要让服务端的这个link具备</p>
<ol>
<li>继续用户m站行为</li>
<li>跳转AppStore行为</li>
<li>重定向到中间页行为</li>
<li>链接归因存储匹配能力</li>
</ol>
</div>
<footer class="article-footer">
<a data-url="http://www.liuxiaoming.com/2017/09/10/ios-universal-links-2/" data-id="cj7eeh76w0003fgckcl05i1hl" class="article-share-link">Share</a>
<ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/DynamicLinks/">DynamicLinks</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Google/">Google</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/UniversalLinks/">UniversalLinks</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/iOS/">iOS</a></li></ul>
</footer>
</div>
</article>
<article id="post-ios-universal-links" class="article article-type-post" itemscope itemprop="blogPost">
<div class="article-meta">
<a href="/2017/09/07/ios-universal-links/" class="article-date">
<time datetime="2017-09-07T15:48:26.000Z" itemprop="datePublished">2017-09-07</time>
</a>
</div>
<div class="article-inner">
<header class="article-header">
<h1 itemprop="name">
<a class="article-title" href="/2017/09/07/ios-universal-links/">iOS App间相互跳转漫谈 - part1</a>
</h1>
</header>
<div class="article-entry" itemprop="articleBody">
<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>文章介绍当下iOS系统中各种App之间的跳转技术,并最终重点介绍UniversalLinks的一种特殊的使用技巧来帮助App来引流,提升转化。</p>
<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>介绍下当下支持的App页面跳转技术及其优劣:</p>
<ol>
<li><p>Scheme跳转<br>例如:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><appscheme>://detail?id=10000</div></pre></td></tr></table></figure>
<p>用户在系统中其他App中点击scheme链接;浏览器网页中点击scheme链接会弹出一个Alert弹框,让用户确认是否跳转。<br>优势:与http的url提供类似,可以通过URL直观表达跳转的页面和意思。触发条件可以是用户点击,也可以通过程序触发JS或者App。<br>劣势:跳转时系统会弹出确认框让用户确认,体验略差。并且不能知道App是否安装,只能通过一些手段推测需要跳转的App是否已经安装,如果跳转时没有安装则会弹出“Safari不能打开该网页,因为网址无效。”的提示体验打折扣,关于H5页面如何推测App已经安装后面会介绍。</p>
</li>
<li><p>Smart App Banners<br>在HTML页面中植入一个meta标签,类似于:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><meta name="apple-itunes-app" content="app-id=myAppStoreID, affiliate-data=myAffiliateData, app-argument=myURL"></div></pre></td></tr></table></figure>
<p>访问这个页面的时候在网页加载完后往下拉页面可以看到App的跳转入口,可以承接跳转的App的功能。<br>可以手机访问这个<a href="http://www.liuxiaoming.com/demo/app-smart-banner.html">Demo页面</a>来体验下<br>优势:对用户而言,点击进入不需要二次确认;自动检测App安装状态,未安装引导到App Store,已安装则可直接打开App,并带入预置在app-argument中的url值,让App感知当前的页面,可以App中继续行为。<br>劣势:最大的问题是跳转操作不可通过程序控制,只能由用户点击触发跳转;另一个产品经理不怎么喜欢的是居然还显示评分星级,这是个头疼的事情。</p>
</li>
<li><p>Univeral Links<br>从iOS9开始,苹果开放了一种新的App之间的跳转方式,从使用体验上来看,苹果希望这种技术用于不打断用户的操作行为为前提,提供更好的App跳转方式。举例来说,正在访问用户正在访问m.alibaba.com,那么如果用户已经安装了Alibaba.com App那么系统将自动引导到App,并将正在访问的完整http链接提供到打开的App以提供在App上继续之前行为的能力。<br>这种技术带来了完美的体验,通俗点说,现在还能提供http的URL(当然需要https)直接打开App,而且没有二次确认!当然也有些地方有待完善,这个下文会详细提到。</p>
</li>
</ol>
<blockquote>
<p>三种技术各有优劣,组合使用可以让引流体验变得非常好。下面会简单介绍三种技术的部署方法和一些技巧。</p>
</blockquote>
<h2 id="部署"><a href="#部署" class="headerlink" title="部署"></a>部署</h2><h3 id="Scheme方式跳转"><a href="#Scheme方式跳转" class="headerlink" title="Scheme方式跳转"></a>Scheme方式跳转</h3><p>这种技术部署是需要App本身对scheme协议头进行注册的,而且这种注册是属于系统级别本地注册关联。</p>
<ol>
<li>项目的Info.plist文件中,新增一个Key -> “URL Types”预置的Key输入时有联想。</li>
<li>新增完后会自动变成一个数组,展开查看第一个元素,将其中 “URL identifier” 置为App的BundleID</li>
<li>并且平级新增一个Key -> “URL Schemes”预置Key有联想。</li>
<li>新增完后自动变成数组,展开,在第一个元素中填入想要注册的scheme协议头即可,比如想要注册 “enalibaba://“,那么只要输入 “enalibaba” 即可。</li>
</ol>
<p>这时App安装后,协议头即注册生效,可以通过浏览器地址栏中输入该协议头即可跳转。</p>
<p>注意:因为是本地注册,所以如果遇到某个其他App也注册这个协议头那会发生什么情况呢?经过实践测试结果发现,协议头是本地抢注式,先被安装的注册的App拥有更高的优先权,当先装的App被删除时,第二注册者会命中,以此后推。</p>
<h3 id="App-Smart-Banner"><a href="#App-Smart-Banner" class="headerlink" title="App Smart Banner"></a>App Smart Banner</h3><p>这种方式是直接去中心化,去校验,完全自由的一种部署方式。</p>
<p>唯一需要做的只是在html页面新增meta标签即可</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><meta name="apple-itunes-app" content="app-id=myAppStoreID, affiliate-data=myAffiliateData, app-argument=myURL"></div></pre></td></tr></table></figure>
<p>使用iOS手机访问这个地址 <a href="http://www.liuxiaoming.com/demo/app-smart-banner.html">Demo页面</a></p>
<h3 id="Universal-Links"><a href="#Universal-Links" class="headerlink" title="Universal Links"></a>Universal Links</h3><p>这种方式更为麻烦一些,因为相当于支持了http链接直接打开App,所以需要双向授信才行。</p>
<p>网站端</p>
<ol>
<li>创建一个名为<code>apple-app-site-association</code>的JSON格式文件,防止在可以被根目录直接访问到的地方。例如打算绑定m.alibaba.com那么就需要<code>https://m.alibaba.com/apple-app-site-association</code>这个文件可以正常访问,mine/type 为 json/text。</li>
<li>文件的内容格式如下<figure class="highlight"><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></pre></td><td class="code"><pre><div class="line">{</div><div class="line"> <span class="attr">"applinks"</span>: {</div><div class="line"> <span class="attr">"apps"</span>: [],</div><div class="line"> <span class="attr">"details"</span>: [</div><div class="line"> {</div><div class="line"> "appID": "9JA89QQLNQ.com.apple.wwdc", //bundleid</div><div class="line"> "paths": [ "/wwdc/news/", "/videos/wwdc/2015/*"] //path 可以通配符</div><div class="line"> },</div><div class="line"> { // 可配置多个域名和path匹配规则的绑定</div><div class="line"> "appID": "ABCD1234.com.apple.wwdc",</div><div class="line"> "paths": [ "*" ]</div><div class="line"> }</div><div class="line"> ]</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
</li>
</ol>
<p>App端</p>
<ol>
<li>找到 AppName.entitlements 文件</li>
<li>在里面 <code>com.apple.developer.associated-domains</code> 的数组下面添加一个item</li>
<li>值为 <code>applinks:m.alibaba.com</code> 即可</li>
</ol>
<p>当然App端能找到entitlements文件需要打开Capabilities中的Associated Domain能力,这需要开发者账号项目才能开启。</p>
<p>到这里双向绑定已做完,编译项目到设备上,通过点击在 备忘录 中预置的匹配Universal Links链接,即可完美跳转了。</p>
<p>为什么要放到备忘录?不能直接浏览器地址栏打开吗?答案是不行,还记得上面讲过,苹果希望的是不打断用户意图的情况下,所以在浏览器地址栏中直接输入地址的行为,苹果认为用户有明显意图想要访问http网页,所以不会直接跳转到App。当然程序也无法控制,如果是JS程序跳转某个UniversalLinks,那结果就是请求正常发出,并不会跳转到App。</p>
<p>而且,而且,苹果的初衷其实是希望不同App之间可以通过这种方式相互跳转。<br>当初我亲测微信跳转Alibaba.com App完美跳转。<br>后来,有一种非常规操作可以阻挡这种跳转,以至于现状是几乎所有App都禁用了UniversalLinks的外跳。<br>但我仍然有解决办法,我会把UniversalLinks的运作原理和攻防技巧放在下一篇文章里详细描述。</p>
<h2 id="豆知识"><a href="#豆知识" class="headerlink" title="豆知识"></a>豆知识</h2><h3 id="Scheme和Schema"><a href="#Scheme和Schema" class="headerlink" title="Scheme和Schema"></a>Scheme和Schema</h3><p>其实正确的叫法应该叫Scheme[skim],而不是Schema[‘ski:mə],<a href="https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/Inter-AppCommunication/Inter-AppCommunication.html" target="_blank" rel="external">官方文档</a>在描述中也是一直使用Scheme。</p>
<p>从词典上查看,这两个词的意思表达非常相近,都有计划策划的意思。</p>
<h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><p><a href="https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariWebContent/PromotingAppswithAppBanners/PromotingAppswithAppBanners.html" target="_blank" rel="external">Promoting Apps with Smart App Banners</a></p>
<p><a href="https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/Inter-AppCommunication/Inter-AppCommunication.html" target="_blank" rel="external">Inter-App Communication</a></p>
<p><a href="https://linkmaker.itunes.apple.com/zh-cht" target="_blank" rel="external">AppID查找工具</a></p>
<p><a href="https://developer.apple.com/library/content/documentation/General/Conceptual/AppSearch/UniversalLinks.html" target="_blank" rel="external">Support Universal Links</a></p>
</div>
<footer class="article-footer">
<a data-url="http://www.liuxiaoming.com/2017/09/07/ios-universal-links/" data-id="cj7eeh76y0004fgck2ajxj2qv" class="article-share-link">Share</a>
<ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/DynamicLinks/">DynamicLinks</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Google/">Google</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/UniversalLinks/">UniversalLinks</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/iOS/">iOS</a></li></ul>
</footer>
</div>
</article>
<article id="post-ios-container" class="article article-type-post" itemscope itemprop="blogPost">
<div class="article-meta">
<a href="/2017/09/05/ios-container/" class="article-date">
<time datetime="2017-09-05T13:24:45.000Z" itemprop="datePublished">2017-09-05</time>
</a>
</div>
<div class="article-inner">
<header class="article-header">
<h1 itemprop="name">
<a class="article-title" href="/2017/09/05/ios-container/">iOS容器化方案</a>
</h1>
</header>
<div class="article-entry" itemprop="articleBody">
<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>随着业务不断的发展,组织结构的调整,对于垂直化业务划分的团队来说,App的解耦需求变得越来越迫切。</p>
<p>举个栗子🌰来说:通过垂直化业务划分后,一个团队负责“首页”+“搜索”+“产品详情“;另一个团队负责“我的”;</p>
<p>这两个团队分别负责不同的代码,没有交叉的代码,端开发同学对号入座,原则上不会产生交叉资源。</p>
<p>这与之前普通的开发方式的区别是,之前端开发同学们相当于一个池子,有需求过来,这个版本需要做哪些东西,大家打散了平均匀给所有同学,按照计划进行开发。</p>
<p>而现在池子变成了两个,两个池子相互不通,并且每个池子已有固定的部分代码需要对应,不会需要越池。从代码上是可以将一个App的代码割裂成两份,两个池子的同学各自开发自己负责的代码,最终发版的时候合并即可。</p>
<h2 id="代码分离"><a href="#代码分离" class="headerlink" title="代码分离"></a>代码分离</h2><p>为什么要分离成两部分代码?原因是对iOS在10人左右团队中,新老同学同时在进行开发,冗余代码加上一次次业务代码、技术代码迭代,代码数量总体是只增不减,慢慢的就会发现,整个工程的代码依赖极为复杂,而且编译速度奇慢。</p>
<p>慢到什么程度呢?让人感到沮丧,对脑中已经构思好的代码,瞬间被这种沮丧冲乱,编译构建的时候不得不跑去干一些别的什么事情,最后终于可以验证的时候发现前面的思路早就乱了。</p>
<p>所以分离代码虽然会有一部分成本,但如果能预期看到这样的沮丧会减少,也是感觉值得的。</p>
<h2 id="多端共享代码"><a href="#多端共享代码" class="headerlink" title="多端共享代码"></a>多端共享代码</h2><p>如果只是一个App,两个开发团队来维护,操作上分离成两份其实是最突出性价比的做法。</p>
<p>但如果一个App里的功能模块还要被复用另一个App中,比如产品详情,有对于维护买卖家两个App的团队,可能就需要共用这部分代码。</p>
<p>这时就非常有必要将原本分离成两部分的代码中的一部分,再次做代码分离,以便满足代码可以被复用,且足够解耦。</p>
<h2 id="方法"><a href="#方法" class="headerlink" title="方法"></a>方法</h2><p>上面说了这么多,核心其实就是分离代码,分离代码的方式方法有很多,最直接的就是使用Cocoapods来对代码进行解耦分离,但这里有个大坑,后面会详细介绍到,这个坑让分离的代码意义和价值变少,也会介绍如何规避和改进。</p>
<p>同时也会介绍有哪些方式方法可以优化和改善,以达到理想的方案。</p>
</div>
<footer class="article-footer">
<a data-url="http://www.liuxiaoming.com/2017/09/05/ios-container/" data-id="cj7eeh76q0001fgck303bjd6i" class="article-share-link">Share</a>
<ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Container/">Container</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/iOS/">iOS</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/容器化/">容器化</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/最佳实践/">最佳实践</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/组件化/">组件化</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/解耦/">解耦</a></li></ul>
</footer>
</div>
</article>
<article id="post-App网络实时数据模拟方案" class="article article-type-post" itemscope itemprop="blogPost">
<div class="article-meta">
<a href="/2017/08/21/App网络实时数据模拟方案/" class="article-date">
<time datetime="2017-08-21T15:57:22.000Z" itemprop="datePublished">2017-08-21</time>
</a>
</div>
<div class="article-inner">
<header class="article-header">
<h1 itemprop="name">
<a class="article-title" href="/2017/08/21/App网络实时数据模拟方案/">App网络实时数据模拟方案</a>
</h1>
</header>
<div class="article-entry" itemprop="articleBody">
<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>客户端同学与服务端同学进行联调时,总是会遇到各种各样的问题。其中,以服务端的线下环境不稳定是最为头疼,联调过程中,一会应用要重启了,一会某个依赖的公共线下环境挂了,或者为了让服务端增加某个字段需要等半天才行。</p>
<p>这些直接导致的结果就是,联调开发效率非常低下!陷入了各种相互等的循环造成了各种碎片的垃圾时间。</p>
<p>于是就想想办法让客户端同学开发的时候尽量少依赖,甚至不依赖服务端的运行环境,这样就可以完美高效开发了。</p>
<h2 id="原始方案"><a href="#原始方案" class="headerlink" title="原始方案"></a>原始方案</h2><p>最开始的做法也是相当普通的做法,就是把服务端的内容结果json字符串以json文件的形式加入app安装包中,app启动后会根据app当前的网络情况判断是获取本地的json文件中的内容还是直接获取真实接口内容。</p>
<p>这种做法其实已经很大程度的解决了最核心问题,如果是这个方案我不需要专门写一篇文章来介绍。</p>
<h2 id="有人吐槽"><a href="#有人吐槽" class="headerlink" title="有人吐槽"></a>有人吐槽</h2><p>产品的不断迭代其实就是一直被吐槽,被吐槽说明被需要,有人用,只是觉得不爽。</p>
<p>有了原始方案后,客户端同学们已经大量从服务端依赖中解救出来,陷入了新的烦恼,原因是只要需要对返回结果做一些哪怕是一点点改动,都需要重新编译压包,这是很恼火的事情。</p>
<p>我自己开发过程中也深刻体会到了这一点,并也开始思考,怎么样不重新编译压包就可以修改mock数据。</p>
<p>思考了几个方案:</p>
<ol>
<li>代理:使用Charles,通过map功能将请求返回结果通过map到的本地文本文件返回,就可以完美实现实时修改mock结果。但需要查看电脑ip,还要手机设置代理,还要配置ssl证书等一堆破事,作为程序员总觉的还是要操作很多步骤非常不爽。</li>
<li>远程修改安装包中的mock文件内容:通过本机命令行操作模拟器和真机,读写app安装包中的mock文件内容来达到目的,这条路探索了一阵子没有找到落地的方案。</li>
</ol>
<h2 id="新的思路"><a href="#新的思路" class="headerlink" title="新的思路"></a>新的思路</h2><p>那天的一件事突然激发了灵感,想出了目前最完美的方案。那天具体目的是啥忘了,总之想要在本机开启一个http静态服务器,设置到任意一个本地目录为root,把里面的内容可以被别人下载访问,看了下mac自带的Apache,需要设置虚拟机等配置,并不是很方便,没法达到我最简单最纯粹的目的。</p>
<p>最终还是探索出了一行命令在任意目录开启http服务的途径,后面会详细介绍。</p>
<p>由此突然想到,如果我把静态服务器root置为项目目录下的mock文件夹,再将本机ip让app感知到,那在运行的时候就可以将请求转发给ip指向的机器,来做到实时mock数据的功能!</p>
<h2 id="全新设计方案"><a href="#全新设计方案" class="headerlink" title="全新设计方案"></a>全新设计方案</h2><p>由此设计出一套不需要任何辅助app支援,不需要手机设置代理,而且可以不用重新编译就可以实时修改mock数据的网络数据模拟方案。</p>
<p>我本身是开发iOS所以下面实施方案都以iOS为例,安卓上思路可以参考。</p>
<h3 id="1-让app知道哪台机器开启了mock数据的http服务"><a href="#1-让app知道哪台机器开启了mock数据的http服务" class="headerlink" title="1. 让app知道哪台机器开启了mock数据的http服务"></a>1. 让app知道哪台机器开启了mock数据的http服务</h3><p> 说的明白一点就是让编译的app知道当前压包的机器是什么ip,这个思路就很多了,我也是采用了最普通的做法,通过在编译过程中插入脚本,读取本机ip,写入当前app的Info.plist文件。</p>
<figure class="highlight bash"><figcaption><span>一行命令获取本机ip</span></figcaption><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">ifconfig | grep <span class="string">"inet "</span> | grep -Fv 127.0.0.1 | awk <span class="string">'{print $2}'</span></div></pre></td></tr></table></figure>
<p>将获得ip写入Info.plist</p>
<figure class="highlight bash"><figcaption><span>写入数据到plist</span></figcaption><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">plist=<span class="string">"<span class="variable">${PROJECT_DIR}</span>/<span class="variable">${INFOPLIST_FILE}</span>"</span></div><div class="line">key=<span class="string">"XM_MOCK_IP"</span></div><div class="line">value=<span class="string">"192.168.2.101"</span></div><div class="line">/usr/libexec/Plistbuddy -c <span class="string">"Delete :<span class="variable">${key}</span>"</span> <span class="string">"<span class="variable">${plist}</span>"</span></div><div class="line">/usr/libexec/Plistbuddy -c <span class="string">"Add :<span class="variable">${key}</span> string <span class="variable">${value}</span>"</span> <span class="string">"<span class="variable">${plist}</span>"</span></div></pre></td></tr></table></figure>
<h3 id="2-在编译机上工程项目Mock目录下启动http静态服务"><a href="#2-在编译机上工程项目Mock目录下启动http静态服务" class="headerlink" title="2. 在编译机上工程项目Mock目录下启动http静态服务"></a>2. 在编译机上工程项目Mock目录下启动http静态服务</h3><p>脚本cd到Mock目录下,执行下面命令就可以。</p>
<figure class="highlight bash"><figcaption><span>在当前目录开启http服务</span></figcaption><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">lsof -ti:7777 | xargs <span class="built_in">kill</span></div><div class="line">SCREEN -d -m python -m SimpleHTTPServer 7777</div></pre></td></tr></table></figure>
<p>第一行命令清理一下占用7777端口的应用。<br>第二行命令可以以当前执行命令的目录为root开启一个7777端口的静态服务器,这句话试了目前主流的macOS版本都支持这样启动。</p>
<p>ps. 解释下Mock目录,Mock目录我的设计是目录中存放的是所有以apiname.json为文件名的请求结果字符串文本文件。<br>这个目录存放在项目的Resource目录下,直接以物理目录在Xcode下可见。<br>目的是为了在开发过程中方便随时新增和修改内容,不需要再切换其他编辑器来修改。</p>
<h3 id="3-让app发起网络请求时转发到mock服务器"><a href="#3-让app发起网络请求时转发到mock服务器" class="headerlink" title="3. 让app发起网络请求时转发到mock服务器"></a>3. 让app发起网络请求时转发到mock服务器</h3><p>这里没什么代码可以提供,大体就是app上得有个开关,可以随时开关app的mock和非mock状态,根据这个标志位,网络请求生成的请求URL会有所不同:</p>
<ul>
<li><p>在mock模式下<br>生成的请求url会变成<code>http://192.168.2.101:7777/xmmock/xxx.json</code><br>网络请求正常发出,或得到的内容就是mock内容,此时修改macOS里Xcode项目中mock文件夹下的json文件内容就可以实时生效修改。<br>这里的<code>xmmock/xxx.json</code>就是root目录下的mock文件结构,可根据规则动态生成。</p>
</li>
<li><p>在非mock模式下<br>正常生成url请求</p>
</li>
</ul>
<h3 id="4-局部数据模拟"><a href="#4-局部数据模拟" class="headerlink" title="4. 局部数据模拟"></a>4. 局部数据模拟</h3><p>这里还可以做一下容错,大体是如果mock模式下,请求<code>http://192.168.2.101:7777/xmmock/xxx.json</code>后如果是非200,说明这个mock文件不存在,则继续发送正常请求,以确保没有在mock范围内的接口在app中还是可以正常访问。</p>
<p>这样就做到了app接口整体可用,只有在mock模式下,在mock范围内的接口会被mock。</p>
<h3 id="5-清理尾巴"><a href="#5-清理尾巴" class="headerlink" title="5. 清理尾巴"></a>5. 清理尾巴</h3><p>这里有点小尾巴,比如之前生成的在plist文件中加入了一个键值标明当前压包的机器ip,这个键值在git中显示是一个修改,这样每个人修改这个代码很容易冲突。</p>
<p>这个也很好办,在写入编译包后,编译完成的环节再把这个键值从plist中自动移除即可</p>
<figure class="highlight bash"><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">plist=<span class="string">"<span class="variable">${PROJECT_DIR}</span>/<span class="variable">${INFOPLIST_FILE}</span>"</span></div><div class="line">/usr/libexec/Plistbuddy -c <span class="string">"Delete :XM_MOCK_IP"</span> <span class="string">"<span class="variable">${plist}</span>"</span></div></pre></td></tr></table></figure>
<h2 id="全新的联调流程"><a href="#全新的联调流程" class="headerlink" title="全新的联调流程"></a>全新的联调流程</h2><p>做到这样以后,可以想象一下,在项目进入开发后,客户端和服务端同学只要事先约定好接口各种情况数据返回的格式,客户端就可以利用这套方案直接当做服务端已经提供接口那样进行开发,当客户端通过本mock方案联调自测各种情况通过后,再与服务端真实接口对接,成本可以说是非常的小,完全消除的之前的相互等待的垃圾碎片时间。</p>
<p>目前我们团队的iOS组内已经使用这套方案成熟运行了好几个版本,开发效率提升明显。</p>
<h2 id="附上完整脚本"><a href="#附上完整脚本" class="headerlink" title="附上完整脚本"></a>附上完整脚本</h2><p>将以下deploy脚本插入到”Build Phases”里尽可能前面的环节,实际操作总我放在”Copy Bundle Resources”的前面。</p>
<figure class="highlight bash"><figcaption><span>XMNetworkMockDeploy.sh</span></figcaption><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></pre></td><td class="code"><pre><div class="line"><span class="meta">#!/bin/sh</span></div><div class="line"><span class="comment">## 给plist赋值函数</span></div><div class="line"><span class="function"><span class="title">setValueToPlist</span></span>() {</div><div class="line"> plist=<span class="string">"<span class="variable">${PROJECT_DIR}</span>/<span class="variable">${INFOPLIST_FILE}</span>"</span></div><div class="line"> </div><div class="line"> key=<span class="string">"<span class="variable">$1</span>"</span></div><div class="line"> value=<span class="string">"<span class="variable">$2</span>"</span></div><div class="line"> /usr/libexec/Plistbuddy -c <span class="string">"Delete :<span class="variable">${key}</span>"</span> <span class="string">"<span class="variable">${plist}</span>"</span></div><div class="line"> /usr/libexec/Plistbuddy -c <span class="string">"Add :<span class="variable">${key}</span> string <span class="variable">${value}</span>"</span> <span class="string">"<span class="variable">${plist}</span>"</span></div><div class="line"> <span class="built_in">echo</span> <span class="string">"XMNetworkMock::在PLIST设定<span class="variable">${key}</span>值为<span class="variable">${value}</span>"</span></div><div class="line">}</div><div class="line"></div><div class="line"><span class="built_in">echo</span> <span class="string">"XMNetworkMock::CONFIGURATION:<span class="variable">${CONFIGURATION}</span>"</span></div><div class="line"><span class="built_in">echo</span> <span class="string">"XMNetworkMock::PROJECT_DIR:<span class="variable">${PROJECT_DIR}</span>"</span></div><div class="line"></div><div class="line"><span class="keyword">if</span> [[ <span class="variable">$CONFIGURATION</span> == <span class="string">"Debug"</span> ]]; <span class="keyword">then</span></div><div class="line"></div><div class="line"><span class="comment">## 获取主机IP地址</span></div><div class="line">ASB_HOST_IP=`ifconfig | grep <span class="string">"inet "</span> | grep -Fv 127.0.0.1 | awk <span class="string">'{print $2}'</span>`</div><div class="line">setValueToPlist <span class="string">"XM_MOCK_IP"</span> <span class="variable">$ASB_HOST_IP</span></div><div class="line"><span class="built_in">echo</span> <span class="string">"XMNetworkMock::主机IP地址<span class="variable">${ASB_HOST_IP}</span>"</span></div><div class="line"></div><div class="line"><span class="comment">## 清理端口进程</span></div><div class="line">lsof -ti:7777 | xargs <span class="built_in">kill</span></div><div class="line"><span class="built_in">echo</span> <span class="string">"XMNetworkMock::端口占用清理完毕"</span></div><div class="line"></div><div class="line"><span class="comment">## 进入目录 </span></div><div class="line">ASB_DIR_MOCK_API=<span class="string">"<span class="variable">${PROJECT_DIR}</span>/Resources/xmmock"</span></div><div class="line">mkdir -p <span class="variable">$ASB_DIR_MOCK_API</span></div><div class="line"><span class="built_in">echo</span> <span class="string">"XMNetworkMock::打开目录<span class="variable">${ASB_MOCK_API}</span>"</span></div><div class="line"></div><div class="line"><span class="comment">## 开启网络服务</span></div><div class="line"><span class="built_in">cd</span> <span class="variable">$ASB_DIR_MOCK_API</span></div><div class="line">SCREEN -d -m python -m SimpleHTTPServer 7777</div><div class="line"><span class="built_in">echo</span> <span class="string">"XMNetworkMock::启动网络服务 <span class="variable">${ASB_DIR_MOCK_API}</span>:7777"</span></div><div class="line"></div><div class="line"><span class="keyword">else</span></div><div class="line"><span class="built_in">echo</span> <span class="string">"XMNetworkMock::检测到非开发模式,已清理调试选项ASC_HOST_IP的值"</span></div><div class="line"><span class="keyword">fi</span></div></pre></td></tr></table></figure>
<p>以下clean脚本放置在”Build Phases”的最后一个流程即可</p>
<figure class="highlight bash"><figcaption><span>XMNetworkMockClean.sh</span></figcaption><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"><span class="meta">#!/bin/sh</span></div><div class="line"><span class="function"><span class="title">deleteValue</span></span>() {</div><div class="line">plist=<span class="string">"<span class="variable">${PROJECT_DIR}</span>/<span class="variable">${INFOPLIST_FILE}</span>"</span></div><div class="line">key=<span class="string">"<span class="variable">$1</span>"</span></div><div class="line">/usr/libexec/Plistbuddy -c <span class="string">"Delete :<span class="variable">${key}</span>"</span> <span class="string">"<span class="variable">${plist}</span>"</span></div><div class="line">}</div><div class="line"><span class="keyword">if</span> [[ <span class="variable">$CONFIGURATION</span> == <span class="string">"Debug"</span> ]]; <span class="keyword">then</span></div><div class="line">deleteValue <span class="string">"XM_MOCK_IP"</span></div><div class="line"><span class="keyword">fi</span></div></pre></td></tr></table></figure>
<p>ps. 注意脚本中相关目录。</p>
</div>
<footer class="article-footer">
<a data-url="http://www.liuxiaoming.com/2017/08/21/App网络实时数据模拟方案/" data-id="cj7eeh76j0000fgckqphhwwkp" class="article-share-link">Share</a>
<ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Andoird/">Andoird</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Mock/">Mock</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Network/">Network</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/RealTime/">RealTime</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/iOS/">iOS</a></li></ul>
</footer>
</div>
</article>
</section>
<aside id="sidebar">
<div class="widget-wrap">
<h3 class="widget-title">Tags</h3>
<div class="widget">
<ul class="tag-list"><li class="tag-list-item"><a class="tag-list-link" href="/tags/Andoird/">Andoird</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Container/">Container</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/DynamicLinks/">DynamicLinks</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Google/">Google</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Mock/">Mock</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Network/">Network</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/RealTime/">RealTime</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/UniversalLinks/">UniversalLinks</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/iOS/">iOS</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/容器化/">容器化</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/最佳实践/">最佳实践</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/组件化/">组件化</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/解耦/">解耦</a></li></ul>
</div>
</div>
<div class="widget-wrap">
<h3 class="widget-title">Tag Cloud</h3>
<div class="widget tagcloud">
<a href="/tags/Andoird/" style="font-size: 10px;">Andoird</a> <a href="/tags/Container/" style="font-size: 10px;">Container</a> <a href="/tags/DynamicLinks/" style="font-size: 15px;">DynamicLinks</a> <a href="/tags/Google/" style="font-size: 15px;">Google</a> <a href="/tags/Mock/" style="font-size: 10px;">Mock</a> <a href="/tags/Network/" style="font-size: 10px;">Network</a> <a href="/tags/RealTime/" style="font-size: 10px;">RealTime</a> <a href="/tags/UniversalLinks/" style="font-size: 15px;">UniversalLinks</a> <a href="/tags/iOS/" style="font-size: 20px;">iOS</a> <a href="/tags/容器化/" style="font-size: 10px;">容器化</a> <a href="/tags/最佳实践/" style="font-size: 10px;">最佳实践</a> <a href="/tags/组件化/" style="font-size: 10px;">组件化</a> <a href="/tags/解耦/" style="font-size: 10px;">解耦</a>
</div>
</div>
<div class="widget-wrap">
<h3 class="widget-title">Archives</h3>
<div class="widget">
<ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/archives/2017/10/">October 2017</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2017/09/">September 2017</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2017/08/">August 2017</a></li></ul>
</div>
</div>
<div class="widget-wrap">
<h3 class="widget-title">Recent Posts</h3>
<div class="widget">
<ul>
<li>
<a href="/2017/10/11/source-application-open/">(no title)</a>
</li>
<li>
<a href="/2017/09/10/ios-universal-links-2/">iOS App间相互跳转漫谈 - part2</a>
</li>
<li>
<a href="/2017/09/07/ios-universal-links/">iOS App间相互跳转漫谈 - part1</a>
</li>
<li>
<a href="/2017/09/05/ios-container/">iOS容器化方案</a>
</li>
<li>
<a href="/2017/08/21/App网络实时数据模拟方案/">App网络实时数据模拟方案</a>
</li>
</ul>
</div>
</div>
</aside>
</div>
<footer id="footer">
<div class="outer">
<div id="footer-info" class="inner">
© 2017 刘哈
<br>
Powered by <a href="http://hexo.io/" target="_blank">Hexo</a>
<br>
<script type="text/javascript">var cnzz_protocol = (("https:" == document.location.protocol) ? " https://" : " http://");document.write(unescape("%3Cspan id='cnzz_stat_icon_1263739123'%3E%3C/span%3E%3Cscript src='" + cnzz_protocol + "s13.cnzz.com/z_stat.php%3Fid%3D1263739123%26show%3Dpic1' type='text/javascript'%3E%3C/script%3E"));</script>
</div>
</div>
</footer>
</div>
<nav id="mobile-nav">
<a href="/" class="mobile-nav-link">Home</a>
<a href="/archives" class="mobile-nav-link">Archives</a>
</nav>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<link rel="stylesheet" href="/fancybox/jquery.fancybox.css">
<script src="/fancybox/jquery.fancybox.pack.js"></script>
<script src="/js/script.js"></script>
</div>
</body>
</html>