感叹在开发中冥思苦想后灵光一现的那些思路 - ZhiJianShuSheng/Read-And-Learn GitHub Wiki

前言

我觉得开发技术的提升不仅仅是知识含量的增加,更重要的是开阔自己的思路,在解决同样的问题时方案能够更胜一筹,让程序能够更有效率,更优雅。这些思路往往都是在冥思苦想后的灵光一现。下面我举几个例子,包括 JSPatch 的方法调用思路,微信读书如何处理数据层卡顿,微信终端跨平台组件 mars 里日志模块 xlog 的性能优化方案,以及自己在 SMCheckProject 项目中对使用方法的解析思路。

JSPatch中的方法调用

JSPatch作者 bang《JSPatch实现原理详解》 这篇博客中提到方法调用的实现比如 UIView.alloc() 在开始时为了能够让这个方法调用,需要给 UIView 对象添加 alloc 方法。由于 JS 没有 OC 那样的转发机制,所以 bang 开始的时候采用了一个复杂的方式,在 require 生成类对象时通过类名用 Runtime 的方法找出此类所有方法,然后 JS 为每个方法生成一个函数,这样的函数用方法名去 OC 调用对应的方法。

这样的想法可行,但是效率性能难以接受,消耗的增长是指数级的。因为不光是当前的类需要遍历,父类直到顶层都需要,会出现内存消耗过度的问题。后来 bang 苦苦找解决方案,突然脑洞大开,想到如果能够实现调用一个不存在的方法能够转发到一个指定函数执行就好了,于是把 JS 里方法调用都替换掉改成调用 __c() 函数,把相关信息传给 OCOC 再用 Runtime 来调用相应的方法返回结果。这样做不用遍历,不用保存方法,我觉得这种脑洞大开想到的方案的这种思路是非常值得学习,当一种思路越陷越深时,要时刻提醒自己,跳出来再看看,是不是还有其它的路可以走。

微信读书处理数据层卡顿

WeRead团队博客里有篇文章 《微信读书 iOS 质量保证及性能监控》 介绍了他们对数据层的性能监控。他们数据层使用的是 YDataCenter,在这个数据层框架里一次 SQL 的完整操作要经过等待 cache 队列,放入 cache 队列执行,等待 db 队列,放入 db 队列四个步骤。将这四个步骤的时间和对应 SQL 记录下来,因为这样的 SQL 操作较多,所以只将超过指定时间的记录下来,这样能以最小的代价找出那些造成阻塞卡顿的 SQL 和场景出来进行优化。

日志文件写磁盘导致大量IO引起程序卡顿

微信团队在介绍他们微信终端跨平台组件 mars 里的日志模块时提到了他们对日志写磁盘处理的考虑过程。一般的简单的处理都会采取两次数据拷贝实现写入磁盘,第一次是从用户空间内存拷贝到内核空间的缓存,第二次是回写内核空间的缓存到磁盘。但是频繁的空间切换使得应用层的性能不可控。

一般为了解决空间切换都会采用先将日志缓存到内存中,达到设置的大小后再压缩加密写入文件,但是这个方案的压缩还是会有让 cpu 飙升的可能,还有个很严重的丢日志的问题。如何能够完美处理呢。

mars 的日志模块 xlog 最终采用了 mmap 这种内存映射文件的方法来作为一个既能够有直接写内存的性能,又具有直接写文件可靠性的方案。从测试来看写内存和写 mmap 的耗时几乎是一样的。

SMCheckProject项目中对使用过的方法的解析

SMCheckProject 这个项目有需要解析出所有使用过的方法,一开始会想到使用递归,以前我做 STMAssembleView 中对自定义的 DSL 语言解析时就是使用的递归,这样时间复杂度就会是 O(nlogn) ,这次我换了个思路,将复杂度降低到了 n ,思路大概是 创建一个字典,键值就是深度,模拟语法树结构,从左到右深度的增加根据 [ 符号,减少根据 ] 符号,值会在 [ 时创建一个 Method 结构体,根据 ] 来完成结构体,将其添加到 methods 数组中 。 这种优化在项目小文件少时看不出很大的区别,但是在项目规模非常庞大时却能将总时间省掉不少。

灵光一现后需要付出的努力

当然光是有个好的思路和想法是完全不够的,为了能够实现思路也是八方英雄各显神通。从有了利用编译原理中分析语法转换语法这个思路进行动态化开始,逐步演化到开发一套全自动编译器实现OC源码转字节码后通过自建虚拟机与Native运行时互联。下面我列出些相关的文章,可以看到大量的基础知识积累是这些好的思路必备的基础,光是有着灵活的大脑也是不够的。