限于iOS AppStore的审核机制,一些新的功能的添加或者bug的修复,想做些节日专属的活动等,几乎都是不太可能的.从已有的经验来看,也是有了一些比较常用的解决方案.本文先是会简单说明对比大部分方案,然后会注重阐述基于JSPatch的在线更新机制的设计和实现.对于任何一家有一定用户基础的iOS应用来说,在线更新技术所产生的直接和间接价值都将远远超过100W.理解,并掌握它;实在没有时间,就记住它,因为这篇文章不仅仅是讨论.
实例地址:https://github.com/ios122/ios122
安装本地所有补丁 –> 联网更新补丁信息,并安装有更新或新增加的补丁.注意此处的安装,指的是执行以下JS文件中的代码.此段代码会替换某个类的默认实现.当App运行到需要某个类的某个被JSPatch替换的方法时,会走JS定义的逻辑,而不再是源代码中默认的逻辑.可以看下DEMO.另外,我们的应用和示例中都使用了Objection这个依赖注入的库,你可能也要先温习下: [Objection,一个轻量级的Objective-C依赖注入框架
](http://www.ios122.com/2015/11/objection/)
mac上,获取某个文件的md5值,直接在终端输入命令:
1
2
|
md5
文件完整路径
.
|
关于校验md5的代码,其实最核心的是如何在oc中使用代码获取某个文件的md5值,然后进行比对.网上的示例很多,但可能不太靠谱,下面贴一段确实可行的,注意要引入系统库 #include <CommonCrypto/CommonDigest.h>
:
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
|
/**
* 获取文件的md5信息.
*
* @param path 文件路径.
*
* @return 文件的md5值.
*/
-
(
NSString
*
)
mcMd5HashOfPath
:
(
NSString
*
)
path
{
NSFileManager
*fileManager
=
[
NSFileManager
defaultManager
]
;
// 确保文件存在.
if
(
[
fileManager
fileExistsAtPath
:path
isDirectory
:nil
]
)
{
NSData
*data
=
[
NSData
dataWithContentsOfFile
:path
]
;
unsigned
char
digest
[
CC_MD5_DIGEST_LENGTH
]
;
CC_MD5
(
data
.
bytes
,
(
CC_LONG
)
data
.
length
,
digest
)
;
NSMutableString
*output
=
[
NSMutableString
stringWithCapacity
:CC_MD5_DIGEST_LENGTH
*
2
]
;
for
(
int
i
=
0
;
i
<
CC_MD5_DIGEST_LENGTH
;
i
++
)
{
[
output
appendFormat
:
@"%02x"
,
digest
[
i
]
]
;
}
return
output
;
}
else
{
return
@""
;
}
}
|
可以毫不夸张的说,正确理解并定义补丁状态,是整个在线更新机制最核心的一步,其他的真的只是辅助:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
/**
* 补丁状态.
*/
typedef
enum
: NSUInteger
{
YFPatchModelStatusUnKnownError
,
//!< 未知错误.
YFPatchModelStatusUnInstall
,
//!< 尚未开始安装.应用初始时,所有本地补丁状态均为此;补丁更新或新增的补丁;在下载完成后,状态也会设置为此.
YFPatchModelStatusSuccess
,
//!< 安装成功.
YFPatchModelStatusFileNotExit
,
//!< 本地补丁文件不存在.
YFPatchModelStatusFileNotMatch
,
//!< 本地补丁MD5与给定的MD5值不匹配.
YFPatchModelStatusUpdate
,
//!< 此补丁有更新.即服务器最新返回的补丁列表中包含此补丁,但补丁的md5或url已改变.
YFPatchModelStatusAdd
//!< 此补丁为新增的.即服务器最新返回的补丁列表中新添加的补丁.
}
YFPatchModelStatus
;
|
补丁状态的具体管理策略,参见https://github.com/ios122/ios122/blob/master/iOS122/iOS122/samples/JSPatchOnline/patch/YFPatchViewModel.m
这个是必然要考虑的问题,一种方式是可以在工程中放一个demo.js供Debug模式下调试;另一种方式是本地返回固定的假数据,但是假数据本身的 JS文件地址,md5,版本号等都是真实的.
1
2
3
4
5
6
7
8
9
10
11
12
|
/**
* 测试模式下,会执行此方法,以验证某个JS文件的作用.默认使用本地demo.js.
*/
-
(
void
)
mcDebug
{
#ifdef DEBUG
NSString
*
path
=
[
[
NSBundle
mainBundle
]
pathForResource
:
@"demo"
ofType
:
@"js"
]
;
[
self
mcEvaluateScriptFile
: path
]
;
#endif
}
|
另外,整个逻辑的实现,还使用了ReactCocoa来简化逻辑代码,如果不是很熟悉,可以先看下:ReactiveCocoa,最受欢迎的iOS函数响应式编程库(2.5版),没有之一!
这个要根据自己App的情况,实际考虑下.我们的App网络接口是基于HTTPS的,所以不存在中间人攻击的情况.所以可以保证md5和文件路径是我们自己可控的,所以只做了最基本的md5校验.具体大家可以参考下官方的基于JSPatch的在线更新补丁实践http://jspatch.com/Docs/security:
1
2
3
4
5
6
7
8
9
10
11
12
|
JSPatch脚本的执行权限很高,若在传输过程中被中间人篡改,会带来很大的安全问题,为了防止这种情况出现,我们在传输过程中对
JS文件进行了
RSA签名加密,流程如下:
服务端:
计算
JS
文件
MD5
值。
用
RSA
私钥对
MD5
值进行加密,与
JS文件一起下发给客户端。
客户端:
拿到加密数据,用
RSA
公钥解密出
MD5
值。
本地计算返回的
JS
文件
MD5
值。
对比上述的两个
MD5
值,若相等则校验通过,取
JS
文件保存到本地。
|
官方有个内测的平台http://jspatch.com,来支持在线更新,但是我做的时候,是不知道的,有点重复造轮子的感觉.但是,也就两天左右就实现了,只要能捋顺补丁状态控制的时机,代码本身其实并没有真正的技术难点.另外,官方的内测平台,好像是闭源的,我不太敢用.
可以先参考下文档https://github.com/bang590/JSPatch/wiki/defineClass使用文档,其实都是一一对应的语法转义.如果代码很多,官方还提供了转换工具:https://github.com/bang590/JSPatchConvertor,但是结果仅供参考,可能还需要二次修改.
我们的App,嵌入了JSPatch来进行Bug修复,已经通过审核,并且刚好修复了一个很紧急的Bug.这里不做过多的口水式的讨论.
如果还在为每次的APP更新而提心吊胆,请细细阅读这篇文章;在线更新的,不仅仅可以用来修复Bug呦~