回应一下 JSPatch 安全问题

2016-1-30 评论(4) 分类:互联网

今天收到不少人给我发这篇新闻《曝苹果应用商店逾千款iOS应用存安全漏洞》,以及其他媒体转载或翻译的一些类似的新闻(1 2),每次我都要解释一遍,比较累,写篇文章说说具体情况。

起因是网络安全公司 FireEye 发表了一篇 JSPatch 安全研究报告,里面介绍了 JSPatch 的使用,并指出一些潜在的危害,分三种情况:

1.开发者自己本身有恶意,在自己开发的APP里加上 JSPatch 引擎,用户安装后开发者自己下发恶意脚本,可以做一些恶意的事情。但这里做恶意事情的权限并没有超出iOS的沙盒,也就是说,有没有用 JSPatch 开发者能做的事是一样的,没有 JSPatch 开发者同样能在代码里实现好恶意功能,通过苹果审核后再开启,所以这条挺废的。

2.开发者接入的一些第三方 SDK 里包含了 JSPatch,SDK 的作者再对这些 APP 下发恶意脚本。这种需要开发者防范,避免使用一些恶意 SDK,目前还没有发现有这样的恶意 SDK。

3.开发者自己在 APP 接入 JSPatch,若开发者没有针对传输的 JSPatch 脚本加密。攻击者就通过网络传输的中间人攻击手段下发恶意脚本到用户APP。这点确实比较危险,接入 JSPatch 时请做好加密传输,只要做 RSA 非对称加密传输就不会有问题。加密方式可以参考之前的文章《JSPatch 部署安全策略》,以及项目自带的 JPLoader 实现。

除了以上这三种情况,接入 JSPatch 的 APP 没有其他问题。据了解大部分使用 JSPatch 的 APP 都已经做好非对称加密,并没有什么安全风险。FireEye 上提到 1200 多个 APP 接入 JSPatch,上述媒体文章直接断章取义成上千款 iOS 应用存在安全漏洞,不知道是看不懂原文,还是 KPI 压力的产物。

博客十年

2016-1-14 评论(8) 分类:生活

2006年1月14日,我在闪吧开了个博客,写下第一篇文章,内容很短相当于现在的微博,当时我自己还觉得会三分钟热度,结果持续写了十年。十年前还在上着高二,捣鼓着flash,用着IE,玩着CS,看着CRT显示器,拿着山寨功能机,如今这些都是古董了。当时因为玩 flash 经常逛闪吧和闪客帝国,嫌当时如日中天的新浪博客样式太丑自由度太低,就在闪吧开了博客,直到09年才自己租空间搭了个wordpress。闪吧虽然很快没落了,但网站竟然一直保留着,没有变过,真是难得,还可以上去怀旧一把。

十年以来在这个博客发了四百多篇文章,用它吐槽生活,发表随想,发布作品,分享技术,收获不少,写博客可以针对一个主题完整思考,是记录也是创作,对个人很有好处。现在流行的微博朋友圈都是快销品,写了什么以后不会再去看它,博客不一样,现在回头看以前写的博客,还挺有意思,很多想法和见闻都记录下来了,很让人怀念。这个博客也算我的一个完整作品,很喜欢这个作品。

回顾我的博客,大致可以分成三个阶段。

第一个阶段 06-08 年,写了很多生活琐碎事,很多内容短得像现在的微博,算不上“博客文章”,这个时期可以看到一个敞开心扉的少年日常碎碎念,有时一些情绪也发上去了,像现在的“树洞”一样,因为放心周围不会有人看到我写的东西,是我的一大私人吐槽地,有些现在看起来还挺搞笑的。

第二个阶段 09-11 年,博客写得最多的时期,逐渐有人看了,也逐渐尝试写长的有主题的文章,推出“推特中文圈”后带来不少读者,当时 Google Reader / RSS 还流行着,大家都会订阅去看,写之前开始会考虑到“这是有人看的文章”,但又没有什么束缚,正值大学最自由信心最满的时期,写了一些我自己比较喜欢的文章,也有几篇被传播得较多的文章,感觉是这个博客最美好的时期。

第三个阶段 12-15 年,开始工作了,多了很多束缚,很少写生活的内容,也没那么多时间,技术文章占了大多数,勉力支撑。其实我并不喜欢在这里发太多技术文章,有时很想回到以前的状态,多写一些生活和随想,但发现已经做不到了。

十年以来做到每个月至少一篇博客,可惜的是大学入学军训那一个月没有机会发博客,只有那一个月漏掉了,有空再把当时军训期间手写的日记发上,补全这个缺口,让这个博客完满。

2015

2015-12-31 评论(7) 分类:生活

照例在12月31日写年终回顾。

工作

JSPatch

我一直热衷于做各种东西,从以前的网站FLASH游戏,到推特中文圈,到伊书等APP,一直喜欢这种做一个完整产品的感觉,算是我一大爱好。之前做的都是实际使用的产品,今年的捣鼓多了种类型:开源项目,对我来说开源项目也是一个完整的产品。

JSPatch 可以说是压力下的产物,做这么久技术都没有什么拿得出手的东西,有些捉急,年初就在琢磨做个什么东西,最初的想法是既然 OC 都可以动态调用和替换方法,那可以试试做个东西让 JS 传信息过来去调用和替换方法,这样就可以实时下发脚本修复bug了,产生这个念头的时候我还不知道有 waxPatch 这个开源库,真是孤陋寡闻,后来真正动手做时才知道,不过发现它已经年久失修,而且 JS 又有语言和自带引擎的优势,觉得值得一试,就开始做了。

匆匆把第一个版本放上 github 后,得到很不错的反响,让我有些始料未及,于是投入更多精力去完善它和运营它,不断改进核心代码,日渐完善。产品上除了核心代码,在测试用例和文档上也下了不少功夫,开源项目要真正让人使用,文档必须完善,核心代码只是开源项目的一部分,周边的配套完善后才能是一个完整的开源项目。运营上主要靠技术文章,先后发表了四五篇 JSPatch 相关文章,对开源项目来说写技术文章是个不错的推广方式,再加上微博和一些技术文章平台助力,效果不错。另一种常见的推广方式是技术演讲,这个还没尝试去做,主要是我个人不太擅长。比较遗憾的是我英文水平不行,没法用同样的推广方式推广到国外。

JSPatch 核心上我觉得最重要的就是一个点子和一个理念。点子就是在运行 JS 前用正则替换代码模拟函数转发,这一点是做这个库最大的瓶颈,也可以说是这么久都没出现这样一个库的原因之一,这个原理很简单,但现实世界中很多事物追溯到源头就是一个小点子。理念是保持整个库的精巧简洁,JSPatch 开源以来经历过两百次代码提交,在不断完善的过程中还是保持了极小的代码量,一千多行 OC 和接近两百行 JS,在加功能和修改功能时的代码量和整洁度是我很看重的一个点,让项目接入无压力,尽量让整个库保持精简小而美,是我的一点小追求。

除了主程序,另外还做了两个外围产品:JSPatch 代码自动转换工具JSPatch 服务平台,代码自动转换工具花了一周时间做,过程比较烧脑,做完挺有成就感,虽然有些特性不支持,但用起来还是挺好的。JSPatch 服务平台提供了托管 JSPatch 脚本的后台,封装了脚本下发逻辑,可以让中小 APP 不用搭建后台也能很容易用上 JSPatch,不过后续一直没找到可持续发展路线,导致一直在内测状态,没有投入多大精力在上面。

至今 JSPatch 在开源社区运作得还算不错,github 上已有 3200+ star,虽说 github star 已通货膨胀,但还是能反映一些热度的。另外也得到不少开源社区的帮助,已有17个人参与开发,Q群交流也有300多人,没有统计到有多少产品使用 JSPatch,做了个页面让使用的人自愿提交,使用的朋友可以有兴趣可以提交下:http://using.jspatch.org

JSPatch 相对 wax 有较多优势,有自信可以说是 iOS 动态修复 bug 最佳解决方案,接下来会继续改善一些小缺陷。对于下一步发展计划,我一直想让 JSPatch 不止用于修复 bug,还可以用于动态添加模块,相对于 ReactNative 它还是有优势的,就是对客户端开发的同学来说学习成本低,延续 OC 的开发思维就行。不过年末忙碌导致最近没时间在这上面花精力,也还没找到比较好的路子,后续再研究研究。

APP

今年没有做一个新 APP,比去年更疏于打理,因为时间精力都用在其他方面了,也没想出什么比较想做的产品。对于已有的 APP,某天想起来更新个版本,结果运气极差,说我的 APP 可以下载音乐被拒,被拒也就算了,老的版本竟然直接被下架,直接从AppStore下架!简直对审核人员无语,折腾了个把月才恢复,已是元气大伤,真是时运不济,命途多舛,不过这样的状况也可以让我抛弃过往重新尝试新的玩法,希望接下来能玩得好。

公司

掐指一算,今年竟然是我第一次在腾讯工作满一整年,往年都是各种间断。工作上迎来毕业以来最大的顺境,一扫阴霾,自我感觉状态不错,拿了2次优秀员工,顺利晋级T3,下半年开始也不焦虑了,工作也算开心。

产品上做的是微信读书,去年就开始做的产品,今年8月才上线,过程比较曲折。产品理念上我是很认同的,就是不想做 another book reader,若跟其他读书 APP 没有区别就没必要做了,所以会有一些别的读书APP没有的尝试,这也是为什么捣鼓那么久才上线的原因之一。但是细节上走了不少弯路,费了挺多时间和资源,还有一些不可控力量,各种拍脑袋,希望接下来能克服这些困难把这个产品做起来。

生活

生娃

年末诞下小女生一枚,当爸爸了,正式从二人世界变成家庭生活,影响确实是很大,结婚对生活没多大影响,有孩子就不一样了,很多时候要围绕着孩子转,小孩前几个月应该是最辛苦的,不乖的时候日夜哭闹,我因为要上班还好,最辛苦的还是当妈妈的,还有专门过来照顾的老妈,真是不容易。

变成家庭生活后,两代人的代沟也就逐渐显示出来,对带小孩的方式会有不同意见,总结起来,代沟最大的就是四个字:温饱迷信,似乎每个奶奶都会怕孙子吃不饱穿不暖,可能是上个年代物质匮乏导致,会特别注重这两点,导致有时过了,另外小孩一有不乖的情况,就会怀疑是不是一些风水迷信方面的缘故,我们表示不信就会有一些摩擦,再加上观念固定,说服不了对方,比较难搞。

小家伙出生俩月体重涨了一倍,长势喜人,有时看着觉得很神奇,年初还没有,年末就长这么胖了,还挺可爱,最近几天已经能跟我有些互动了,逗她偶尔会笑,很期待再过一两个月能跟她玩耍,小孩三岁以内是最美好的,希望接下来能多些时间陪她度过这段美好的时光。

车子

有小孩有就开始考虑买个车,我对车不怎么感冒,如果没小孩应该是不会那么快买的,现在买车也不是什么大事了,相对于房子车实在太便宜。当时对车一窍不通,挑车只看外貌,在朗动/福克斯/雷凌中选,最后选了丰田雷凌,中规中矩的车。买完后开的次数较少,半年多过去才2K公里,上班的地方没停车位,比较蛋疼,偶尔去市区也不会开,因为停车费比打车费还贵,论便宜肯定还是不买车只打车便宜,特别是现在有滴滴打车,不过自己偶尔自由地开开车感觉还是挺爽的。

旅游

有了小屁孩,基本宣布跟旅游告别至少两年:(,本来计划去日本,现在遥遥无期了。今年两次出行,都算不上旅游,年中第一次自驾游去了下南沙,自驾游感觉挺不错的,不过南沙比较一般,能玩的地方很少,就去吃吃喝喝走走散散心。另外公司旅游去了下澳门,全因耗时短还能顺便买奶粉才选澳门,十分坑爹,对澳门印象不太好,可能我本来就对赌博印象不好吧,澳门也确实没什么值得游玩的,印象就是路很窄,房子很贵,导游很拜金,充满一种俗气。赌场也跟想象的不太一样,没什么吸引力,转两圈没玩,只在附近商场逛逛了事。只能说,今年旅游方面有些憋坏了。

股市

今年年初股市大热,我也跳入这个坑,结果大家都能猜到了,当时亏到无心工作。A股倒是不亏不赚,主要是港股的坑太大,真是杯具,还好没有投入特别多,像我这样什么都不懂进入股市其实跟赌博差别不大,不应该这样玩的,但环境的影响实在太厉害,迷失了,以后还是要注意点。

其他

因为做微信读书,导致我看书也变多了,看了28本书,不过光《新宋》就占了11本。读书相关的已另写了篇博客,就不再说了。电影倒是看得少了,因为有了小孩,影院也去不了了,在家也没什么娱乐时间,算下来只看了12部,有印象的就只有《大圣归来》。上下班路上还是继续听电台,还是听矮大紧和罗胖,外加了个吴晓波,几乎每一期都听了,感觉还是增长不少见识的,虽然大部分都忘了。

今年几乎都没有锻炼,只有寥寥几次游泳和羽毛球,还好身体还算争气,没出现什么病。明天开始要好好锻炼~

今年博客写得更少了,只有两篇非技术文章,扯淡扯得少了,要是没有“每月至少一篇”的承诺,可能就这样荒废了。小事都写在日记上,日记已连续写了2122天,已成一种习惯停不下来,大多时候写是为了不破坏这个连续性。

2016照旧没什么计划,希望在自由/体验/创造这三点上有更多收获。

读书

2015-11-28 评论(6) 分类:随记

近一年来在做微信读书,导致自己看书也多了些,看了《新宋》系列十几本,东野圭吾的几部经典作品,以及一些杂七杂八的社科类书。

第一次接触《新宋》这样的设定,可以说是穿越小说,也可以说是架空历史小说,讲的是在宋朝王安石变法这个中国历史转折时期,如果矫正变法的弊端,用上现代化的思想和制度去改革会是怎样,包括改善纺织生产,重商言利,创办现代化学校,引入数学物理知识格物致知,写书办报掌握舆论,兵器研究生产火药,探索南海扩张海上贸易,改良青苗法等系列法例,充分考虑当时人的接受程度,循序渐进,中间有很多君臣间,权臣间的博弈,在权术描写上很靠谱。假想在宋代这个史上读书人地位最高,言论最自由的时期,又有锐意改革意图大展宏图的君主,若有合适的方法,会产生什么样的繁荣。虽然不是历史,但也能看到那个时代的社会风貌,士大夫阶层的气节,在宋朝之后包括今天都不再有了。这书最大的缺点在于太拖沓,特别是对战争的描写十分冗长,并且还未完结,另外整部书都十分理性,缺少感情上的描写,没有多少值得回味的地方。

另外东野圭吾系列中,印象最深的是《白夜行》,也是他的代表作,虽然很阴暗,但很多细节和情感丰富细腻,描述方式平淡又很有震撼力,很能影响人心境,看完有不少可以回味的地方,好的小说就是这样吧,上一次有这种感觉的书还得追溯到《三体》和《挪威的森林》。

社科书籍方面,最近看的《人类简史》可以说是我最喜欢的书籍Top3,作者以绝对理性的方式阐述人类的发展史以及宗教/金钱/国家/信念/欲望这些重要事物的由来,内有大量毁三观的观点,虽然不能让人全部赞同,但让人大开眼界,绝对推荐。

现在要看完一本书有点难度,因为看书已经不算是休闲娱乐了,需要用点脑,而现在大多数人从事脑力劳动,脑子都快不够用了,休闲时喜欢做一些不用动脑的事,例如刷微博朋友圈看鸡汤玩休闲游戏,闲了还要耗脑去看书确实不是什么好差事。日常休闲娱乐是要的,但分点精力给看书还是有不少好处的,小说可以给你不一样的体验和想象,好的小说还会留下余味,而社科类非小说书籍多少可以增长点见识,满足好奇心,避免被一些人牵着鼻子走。应该大部分人都认可读书有益的观点,只是时间不知不觉就被微博朋友圈吸走了。既然读书被大众认同是好的,只是不如无脑娱乐那么轻松,那如果要做一个新型读书产品,我觉得应该是要给读书多一些额外的乐趣和鼓励,以此吸引人多读书,对抗无脑娱乐,像微信读书的每周读书时长排名就是一个例子,一些健身产品也是这种思路,希望后续可以做出更多的尝试。

JSPatch Convertor 实现原理详解

2015-10-13 评论(9) 分类:技术文章 Tags:

简介

JSPatch Convertor 可以自动把 Objective-C 代码转为 JSPatch 脚本。

JSPatch 是以方法为单位进行代码替换的,若 OC 上某个方法里有一行出了bug,就需要把这个方法用 JS 重写一遍才能进行替换,这就需要很多人工把 Objective-C 代码翻译成 JS 的过程,而这种代码转换的过程遵循着固定的模式,应该是可以做到自动完成的,于是想尝试实现这样的代码自动转换工具,从 Objective-C 自动转为 JSPatch 脚本。

方案 / Antlr

做这样的代码转换工具,最简单的实现方式是什么?最初考虑是否能用正则表达式搞定,如果可以那是最简单的,后来发现像 方法声明 / get property / NSArray / NSString 等这些是可以用正则处理的,但需要匹配括号的像 block / 方法调用 /set property 这些难以用正则处理,于是只能转向其他途径。

Antlr

接下来的思路是对 Objective-C 进行词法语法解析,再遍历语法树生成对应的 JS 代码。Objective-C 词法语法解析 clang 可以做到 ,但在找方案过程中发现了 antlr 这个神器,以及为 antlr 定制的几乎所有语言的语法描述文件,更符合我的需求。antlr 可以根据语法描述文件生成对应的词法语法解析程序,生成的程序可以是 Java / Python / C# / JavaScript 这四种之一。

也就是说,我们拿 ObjC.g4 这个语法文件,就可以通过 antlr 生成 Objective-C 语法解析程序,程序语言可以在上述四种语言中任挑,我挑选的是 JavaScript,生成的程序可以在 [这里] 看到。官方文档有生成的流程和使用方法,可以自己试下。
(更多…)

逻辑推理的陷阱

2015-9-16 评论(6) 分类:随记

人们喜欢用一个单一的逻辑推理链去解释一个结果,因为人脑太傻,信息越简单就越容易被人理解,但实际上现实世界不像数学世界那么严谨和单纯,导致一个现象或结果的原因通常是非常多的,有些还无法用简单的逻辑推理去推导,这导致现实中的逻辑推理有很多陷阱,看似很有道理的推理实际上根本不成立。

臆想

其中一类逻辑陷阱就是按自己臆想去推理出结论,原因和结论没有关系,但因为现实世界的模糊,经常还能让人听起来觉得挺有道理,不经深究不知道是扯淡。罗辑思维经常做这样的事,举个小例子,有一天罗胖说为什么几乎只有人类有眼白?因为动物要隐藏眼神所以不能有眼白,而人类第一需求是其他人的协助,眼白有助于让别人看懂自己的喜怒哀乐,有利于社交所以发展出眼白。听起来好像有道理,仔细想想却不是这么回事,很多人用进化论去解释一些人的特征时忽略了它的条件,就是特征要对生存率有较大的提高才能进行优胜劣汰。眼白符合这个条件吗?试想一个部落里,每个人都没有眼白,突然基因突变出一个有眼白的人,然后有眼白的人会大受欢迎,大家只跟TA繁衍后代流传基因,没眼白的就会被淘汰掉?完全说不通。若出现眼白的过程不是突变,而是逐渐变淡退去,那更跟什么眼白有助于社交没半毛钱关系,它根本不决定生存率,更何况哈士奇先生也有眼白,所以这不过是在扯淡。

再说说进化论,进化论有点被滥用的迹象,按这种思维方式,很容易用“进化论”推理出跟现实不符的结论出来:作为一个原始人,在婴儿夭折率高的情况下,怎样挑女朋友可以使后代的生存率高,使自己的基因有竞争优势?当然是选择强壮的女士,怀孕期间可以更好保护胎儿,生下来的孩子身体强壮几率高,生存率自然也高,另外原始人吃穿无保障,女朋友最好还要有点胖,有点脂肪能抵御突如其来的食物荒,这样一代代淘汰下来,喜欢强壮肥胖女性的男性后代生存率高,不喜欢肥胖强壮女性的男性基因慢慢被淘汰,所以按推论现在大部分男性应该都更喜欢强壮肥胖女性。这种臆想在日常玩笑时扯扯淡忽悠忽悠挺有意思,要当成论点说服别人时就要注意了。

捏造现实

第二种是为了推导出自己想要的结论,忽视了现实,或者对一部分现实进行捏造。例如周鸿祎之前在推广他微创新的理论时,经常说一个例子,就是 iPhone 是怎么来的呢?它不是啪一声就出现了,而是一个渐进微创新的过程,先做一个小小的只能听歌的 iPod,然后想能不能在 iPod 上加一个屏幕,变成 iPod touch,接着想能不能加个通话功能,才变成 iPhone,这里一步一个微创新,众多微创新一起最后才出现一个 iPhone。好有道理的感觉,但现实是这样吗,现实是 iPod Touch 是在 iPhone 推出后8个月才出现的产品,是 iPhone 的精简版,另外 iPhone 的系统是由 Mac 系统精简得来,跟 iPod 也没什么关系,做这个逻辑推理时完全忽视了这些现实,捏造了“iPhone是由iPod一步步改造得来”这样的现实,老周想当然地为推理自己的论点拼凑论据随意捏造现实,让人听起来逻辑性很强,这很容易说服不了解事实,只考虑逻辑是否成立的人,实际上又是另一种形式的扯淡。

以偏概全

第三种是所有论点结论都是事实,但从导致结果的众多原因中挑出有限的几个,断定就是这几个原因导致的结果,也就是以偏概全。典型的应用是成功学,导致成功的原因非常多,只挑其中的勤奋/情商高/有胆识等几个点推导出某个人必然成功。还有像某些养生文章,XX活到90岁,因为她天天练太极。像一些社会观点,美国好,是因为民主。以偏概全是应用最广的,因为说的都是现实,而且都有一定的关系,推理起来很有道理,人脑又更倾向于相信简单的逻辑,对于错综复杂的真实世界往往处理不过来,所以以偏概全大受欢迎,这也给很多争论提供了广大空间。

JSPatch 部署安全策略

2015-8-31 评论(26) 分类:技术文章

使用 JSPatch 有两个安全问题:

  1. 传输安全:JS 脚本可以调用任意 OC 方法,权限非常大,若被中间人攻击替换代码,会造成较大的危害。
  2. 执行安全:下发的 JS 脚本灵活度大,相当于一次小型更新,若未进行充分测试,可能会出现 crash 等情况对 APP 稳定性造成影响。

接下来说下这两个问题的解决方案。

传输安全

方案一:对称加密

若要让 JS 代码传输过程中不轻易被中间人截获替换,很容易想到的方式就是对代码进行加密,可以用 zip 的加密压缩,也可以用 AES 等加密算法。这个方案的优点是非常简单,缺点是安全性低,容易被破解。因为密钥是要保存在客户端的,只要客户端被人拿去反编译,把密码字段找出来,就完成破解了。

对此也有一些改进方案,例如:

1.可以把密码保存到 keychain 上,但这种方式也是不可靠的,只要随便找一台机器越狱装了这个 APP,用 hook 的方式在 APP 上添加一些代码,获得 keychain 里的密钥值,就可以用于其他所有机器的传输解密了。

2.给每个用户下发不同的密钥。但这样就非常繁琐,需要对下发密钥的请求做好保护,后台需要每次都对脚本进行不同密钥的加密操作,复杂性高了。

综上,对称加密安全性低,若要稍微提高点安全性,就会提升程序复杂度。 (更多…)

JSPatch实现原理详解<二>

2015-7-6 评论(15) 分类:技术文章 Tags:

注:本文较早撰写,随着 JSPatch 的改进,有些内容已与最新代码对不上,建议转看重新整理后的JSPatch实现原理详解


距离上次写的<JSPatch实现原理详解>有一个月的时间,在这段时间里 JSPatch 在不断地完善和改进,代码已经有很多变化,有一些修改值得写一下,作为上一篇的补充。

Special Struct

先说下 _objc_msgForward,在上一篇提到为了让替换的方法走 forwardInvocation,把它指向一个不存在的 IMP: class_getMethodImplementation(cls, @selector(__JPNONImplementSelector)),实际上这样实现是多余的,若 class_getMethodImplementation 找不到 class / selector 对应的 IMP,会返回 _objc_msgForward 这个 IMP,所以更直接的方式是把要替换的方法都指向 _objc_msgForward,省去查找方法的时间。

接着出现另一个问题,如果替换方法的返回值是某些 struct,使用 _objc_msgForward(或者之前的 @selector(__JPNONImplementSelector))会 crash。几经辗转,找到了解决方法:对于某些架构某些 struct,必须使用 _objc_msgForward_stret 代替 _objc_msgForward。为什么要用 _objc_msgForward_stret 呢,找到一篇说明 objc_msgSend_stretobjc_msgSend 区别的文章),说得比较清楚,原理是一样的,是C的一些底层机制的原因,简单复述一下:

大多数CPU在执行C函数时会把前几个参数放进寄存器里,对 obj_msgSend 来说前两个参数固定是 self / _cmd,它们会放在寄存器上,在最后执行完后返回值也会保存在寄存器上,取这个寄存器的值就是返回值:

-(int) method:(id)arg;
    r3 = self
    r4 = _cmd, @selector(method:)
    r5 = arg
    (on exit) r3 = returned int

普通的返回值(int/pointer)很小,放在寄存器上没问题,但有些 struct 是很大的,寄存器放不下,所以要用另一种方式,在一开始申请一段内存,把指针保存在寄存器上,返回值往这个指针指向的内存写数据,所以寄存器要腾出一个位置放这个指针,self / _cmd 在寄存器的位置就变了:

-(struct st) method:(id)arg;
    r3 = &amp;struct_var (in caller&#39;s stack frame)
    r4 = self
    r5 = _cmd, @selector(method:)
    r6 = arg
    (on exit) return value written into struct_var

objc_msgSend 不知道 self / _cmd 的位置变了,所以要用另一个方法 objc_msgSend_stret 代替。原理大概就是这样。

上面说某些架构某些 struct 有问题,那具体是哪些呢?iOS 架构中非 arm64 的都有这问题,而怎样的 struct 需要走上述流程用 xxx_stret 代替原方法则没有明确的规则,OC 也没有提供接口,只有在一个奇葩的接口上透露了这个天机,于是有这样一个神奇的判断:

if ([methodSignature.debugDescription rangeOfString:@&quot;is special struct return? YES&quot;].location != NSNotFound)

NSMethodSignaturedebugDescription 上打出了是否 special struct,只能通过这字符串判断。所以最终的处理是,在非 arm64 下,是 special struct 就走 _objc_msgForward_stret,否则走 _objc_msgForward

(更多…)

JSPatch实现原理详解

2015-6-2 评论(50) 分类:技术文章

注:本文较早撰写,随着 JSPatch 的改进,有些内容已与最新代码对不上,建议转看重新整理后的JSPatch实现原理详解


JSPatch以小巧的体积做到了让JS调用/替换任意OC方法,让iOS APP具备热更新的能力,在实现 JSPatch 过程中遇到过很多困难也踩过很多坑,有些还是挺值得分享的。本篇文章从基础原理、方法调用和方法替换三块内容介绍整个 JSPatch 的实现原理,并把实现过程中的想法和碰到的坑也尽可能记录下来。

基础原理

能做到通过JS调用和改写OC方法最根本的原因是 Objective-C 是动态语言,OC上所有方法的调用/类的生成都通过 Objective-C Runtime 在运行时进行,我们可以通过类名/方法名反射得到相应的类和方法:

Class class = NSClassFromString("UIViewController");
id viewController = [[class alloc] init];
SEL selector = NSSelectorFromString("viewDidLoad");
[viewController performSelector:selector];

也可以替换某个类的方法为新的实现:

static void newViewDidLoad(id slf, SEL sel) {}
class_replaceMethod(class, selector, newViewDidLoad, @"");

还可以新注册一个类,为类添加方法:

Class cls = objc_allocateClassPair(superCls, "JPObject", 0);
objc_registerClassPair(cls);
class_addMethod(cls, selector, implement, typedesc);

对于 Objective-C 对象模型和动态消息发送的原理已有很多文章阐述得很详细,例如这篇,这里就不详细阐述了。理论上你可以在运行时通过类名/方法名调用到任何OC方法,替换任何类的实现以及新增任意类。所以 JSPatch 的原理就是:JS传递字符串给OC,OC通过 Runtime 接口调用和替换OC方法。这是最基础的原理,实际实现过程还有很多怪要打,接下来看看具体是怎样实现的。
(更多…)

JSPatch – 动态更新iOS APP

2015-5-25 评论(52) 分类:作品 技术文章 Tags:

JSPatch是最近业余做的项目,只需在项目中引入极小的引擎,就可以使用JavaScript调用任何Objective-C的原生接口,获得脚本语言的能力:动态更新APP,替换项目原生代码修复bug。

用途

是否有过这样的经历:新版本上线后发现有个严重的bug,可能会导致crash率激增,可能会使网络请求无法发出,这时能做的只是赶紧修复bug然后提交等待漫长的AppStore审核,再盼望用户快点升级,付出巨大的人力和时间成本,才能完成此次bug的修复。

使用JSPatch可以解决这样的问题,只需在项目中引入JSPatch,就可以在发现bug时下发JS脚本补丁,替换原生方法,无需更新APP即时修复bug。

例子

@implementation JPTableViewController
...
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
  NSString *content = self.dataSource[[indexPath row]];  //可能会超出数组范围导致crash
  JPViewController *ctrl = [[JPViewController alloc] initWithContent:content];
  [self.navigationController pushViewController:ctrl];
}
...
@end

上述代码中取数组元素处可能会超出数组范围导致crash。如果在项目里引用了JSPatch,就可以下发JS脚本修复这个bug:

#import “JPEngine.m"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [JPEngine startEngine];
    [NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://cnbang.net/bugfix.JS"]] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
    NSString *script = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    if (script) {
      [JPEngine evaluateScript:script];
    }
}];
   ….
    return YES;
}
@end

//JS
defineClass("JPTableViewController", {
  //instance method definitions
  tableView_didSelectRowAtIndexPath: function(tableView, indexPath) {
    var row = indexPath.row()
    if (self.dataSource().length > row) {  //加上判断越界的逻辑
      var content = self.dataArr()[row];
      var ctrl = JPViewController.alloc().initWithContent(content);
      self.navigationController().pushViewController(ctrl);
    }
  }
}, {})

这样 JPTableViewController 里的 -tableView:didSelectRowAtIndexPath: 就替换成了这个JS脚本里的实现,在用户无感知的情况下修复了这个bug。

更多的使用文档和demo请参考github项目主页

原理

JSPatch用iOS内置的JavaScriptCore.framework作为JS引擎,但没有用它JSExport的特性进行JS-OC函数互调,而是通过Objective-C Runtime,从JS传递要调用的类名函数名到Objective-C,再使用NSInvocation动态调用对应的OC方法。详细的实现原理以及实现过程中遇到的各种坑和hack方法会另有文章介绍。

方案对比

目前已经有一些方案可以实现动态打补丁,例如WaxPatch,可以用Lua调用OC方法,相对于WaxPatch,JSPatch的优势是:

1.JS语言
JS比Lua在应用开发领域有更广泛的应用,目前前端开发和终端开发有融合的趋势,作为扩展的脚本语言,JS是不二之选。

2.符合Apple规则
JSPatch更符合Apple的规则。iOS Developer Program License Agreement里3.3.2提到不可动态下发可执行代码,但通过苹果JavaScriptCore.framework或WebKit执行的代码除外,JS正是通过JavaScriptCore.framework执行的。

3.小巧
使用系统内置的JavaScriptCore.framework,无需内嵌脚本引擎,体积小巧。

4.支持block
wax在几年前就停止了开发和维护,不支持Objective-C里block跟Lua程序的互传,虽然一些第三方已经实现block,但使用时参数上也有比较多的限制。

相对于WaxPatch,JSPatch劣势在于不支持iOS6,因为需要引入JavaScriptCore.framework。另外目前内存的使用上会高于wax,持续改进中。

风险

JSPatch让脚本语言获得调用所有原生OC方法的能力,不像web前端把能力局限在浏览器,使用上会有一些安全风险:

1.若在网络传输过程中下发明文JS,可能会被中间人篡改JS脚本,执行任意方法,盗取APP里的相关信息。可以对传输过程进行加密,或用直接使用https解决。

2.若下载完后的JS保存在本地没有加密,在未越狱的机器上用户也可以手动替换或篡改脚本。这点危害没有第一点大,因为操作者是手机拥有者,不存在APP内相关信息被盗用的风险。若要避免用户修改代码影响APP运行,可以选择简单的加密存储。

其他用途

JSPatch可以动态打补丁,自由修改APP里的代码,理论上还可以完全用JSPatch实现一个业务模块,甚至整个APP,跟wax一样,但不推荐这么做,因为:

  1. JSPatch和wax一样都是通过Objective-C Runtime的接口通过字符串反射找到对应的类和方法进行调用,这中间的字符串处理会损耗一定的性能,另外两种语言间的类型转换也有性能损耗,若用来做一个完整的业务模块,大量的频繁来回互调,可能有性能问题。
  2. 开发过程中需要用OC的思维写JS/Lua,丧失了脚本语言自己的特性。
  3. JSPatch和wax都没有IDE支持,开发效率低。

若想动态为APP添加模块,目前React Native给出了很好的方案,解决了上述三个问题:

  1. JS/OC不会频繁通信,会在事件触发时批量传递,提高效率。(详见React Native通信机制详解
  2. 开发过程无需考虑OC的感受,遵从React框架的思想进行纯JS开发就行,剩下的事情React Native帮你处理好了。
  3. React Native连IDE都准备好了。

所以动态添加业务模块目前还是推荐尝试React Native,但React Native并不会提供原生OC接口的反射调用和方法替换,无法做到修改原生代码,JSPatch以小巧的引擎补足这个缺口,配合React Native用统一的JS语言让一个原生APP时刻处于可扩展可修改的状态。

目前JSPatch处于开发阶段,稳定性和功能还存在一些问题,欢迎大家提建议/bug/PR,一起来做这个项目。

(更多…)