zoukankan      html  css  js  c++  java
  • 开发完 iOS 应用,接下去你该做的事

    iOS专项总结

    一个应用经过多次迭代后告一段落,接下去我们在技术上还可以做些什么呢?答案是提高代码的整体质量。关于这方面,除了我们常喊的 重构,测试也非常重要。

    博主近期给我们的 iOS客户端代码来了一次专项测试。主要从常规的 辅助测试 入手,来了次代码大清理,找到代码中的问题,并一一改掉它们。惊喜的是,这对于提高本人的代码水平有很大的帮助。其实,这套代码的质量本身已经很高了,也非常整洁。而这主要得益于严格的代码规范和pull request机制。

    关于测试,App常关注的往往是一些功能性的,包括单元测试,用monkey在界面上点击看页面表现是否正确等等。 我以前还搭过一个 aspectJ + robotium。(这是Java上的) 然而,测试更应该覆盖代码质量,性能检测等等。

    下面给出一副我理解测试的结构图:

     

    顺着这幅图,我们可以从静态测试入手。

    关于 analyze

    在Xcode 中执行product -> analyze工具,出现了粗粗细细的箭头标识表示调用逻辑。

     

    这个工具所找出的主要是代码中的逻辑问题,本身能发现的问题很多。实际运用到工程代码上,发现的问题主要分为以下四点:

    • 比如初始化赋值未被使用过。

    • 比如未调用super语句。

    • 比如从未使用过的变量。

    • 比如逻辑上错误的代码。(这点极少)

    但是关于资源文件,该工具是扫描不出来的。另外,我们可以在Xcode中配置该工具。

    Clang 静态分析器

    analyze 的底层其实就是 Clang 静态分析器,包含 ’shallow’ 和 ‘deep’ 深浅两种模式。

    深模式比浅模式慢很多,因为多出了跨方法的 控制流分析 以及 数据流分析。(如前文那副图)

    建议:
    开启分析器的 全部检查(方法是在 build setting 的 “Static Analyzer” 部分开启所有选项)
    在 build setting 里,对 release 的 build 配置开启 “Analyzer during ‘Build’“。(真的,一定要这样做 — 你不会记得手动跑分析器的。)
    把 build setting 里的 “Model of Analysis for ‘Analyze’“设为 Shallow(faster)
    把 build setting 里的 “Model of Analysis for ‘Build’“设为 Deep

    但这两种对于公司这么一个大项目来说都不快,假如在 build setting里 开启 Model of Analysis for ‘Build’ 并设为浅模式,那么每次运行就会整体跑一遍静态分析并不合理。

    还有一种设想是在打 ad hoc 包前自动跑一遍 静态分析,是比较合理的也可以在XCode中开启。但是我们编译打包用的是自动化工具,服务器直接从github读取而不是通过XCode,会跳过这个分析。

    比较合理的做法是定期 跑一遍 analyzer 工具。(比如每周五)而不是 build , release 以前做这事。(问了下其他同事的意见)

    目前静态分析在工程中有 这么几块不改的问题: 首先是网上拷过来的算法。包括加密算法,模糊算法,64位编译算法等等。里面有一些远大于,远小于,与运算等。看不懂的地方不改。

    为什么不改呢?因为没看出逻辑问题。(或者看懂了也不敢改,怕算法错误。)

    Slender

    slender 是一款针对 iOS 图片分析的 Mac 软件。导入工程,可以找到所有未使用到的 resource。(unused 这软件也可以找到多余的资源)

    除此以外,它还给出了所有资源文件的建议。包括你缺省的 x3, x2图片版本等。

     

    Faux Pas

    意外发现的惊喜。Faux Pas 是一个出色的静态 Error 检测工具。

    slender 找到的是未使用的资源,faux 找到的是代码中使用到,但不存在的image。(实测下来往往有很多)

    甚至由此 发现了一些弃用的文件夹。

    不仅如此,它有一百多个error 和 warning 规则。包括 未定义的 user define runtime attributes(跑在runtime上)。这一点平时靠程序员的肉眼狠难见。曾经你为了图省事直接在 xib 中炫技,加上了一些 key。然而当代码变迁,删掉一个类的时候,根本想不到 到 xib中去删除 对应的部分。

    还有一些代码规范上的问题。

    比如界面上的文字未本地化;在release 版本依旧有NSLog 输出;NSPhotoLibraryUsageDescription在info.plist中未定义;被定义成strong类型的delegate等等。

    和一些配置上的建议。

    Warning

    你的pods 中还在报 warning 吗?你可以使用下面这种方法屏蔽它们。

    在podfile 的开头加上这样一句话:

    inhibit_all_warnings!

    就能屏蔽 pods 中所有代码上的 warning了。

    然后可惜的是,对于编译上的 warning却束手无策。注:iOS中不能阻止 Xcode 报warning 的选项。只能屏蔽某一文件,或某工程的warning。比如我们已屏蔽的pods中 代码的错误。但是屏蔽不了系统在编译过程中 非代码级别的 警告。

    并且也不建议屏蔽这种警告,比如由于友盟未支持 7.0 版本所导致的 警告。现在由于 友盟本身未支持,7.0 用户还有许多等等原因,但未来或许能解决。不应忘记这点。

    Leaks

    苹果集成了一个性能检测工具,叫instruments。使用instrument中的Leaks工具:

     

    iOS内存泄露点比较少,大部分都是系统内部方法,或者一些库里的。我们对此别无办法。实测下来,主要的泄露场景集中在 UIKeyboard 上。在我尝试了各种点击场景后,系统弹框时候最容易泄露。

    假如你的代码中真有泄露,那它们可能集中在快速滑动 Listing的时候,加载大批量图片的时候。在我们的应用中,这些大都使用了网上开源的成熟库。

    对于 iOS可能泄露的地方,我的建议还是通过已知的代码规范 逆向去寻找。比如 误用 strong类型的delegate; block 中没有使用 weak self等等。

    Time Profiler

    时间都去哪儿了。这个工具可以找出我们最耗时的操作并定位到代码中。

    实际使用过程中,确实发现了一处不合理的耗时循环。

     

    (右侧黑色部分是耗时操作,但不一定是错误的代码)

    工具只是统计时间的消耗,更重要的是通过对代码的敏感,分析定位出可优化的代码。举个例子,有这么一段代码。

    有个循环在判断时候使用了枚举类型,但由于枚举类型获取不到count,所以直接写死了循环10次。

    但其实我们可以在枚举类型的末尾加上TYPE_MAX。这样既拥有了枚举在switch时候的优势,又得到了NSArray获取count的办法。

    加载时间

    加载时间上,主要看看不同网络下出首页有没有卡死等。公司wifi条件真机上测试大约 1.2s 内加载完毕,而当模拟边缘网络时主线程加载依然迅速,没发现什么问题。

    关于启动时间,有两种查看方法。一种是通过上文的时间检测工具,另一种是直接在代码中打log。其中log又有两种打法。

    分别说下这两种打法:

    CFAbsoluteTime StartTime;

    int main(int argc, char **argv) {

         StartTime = CFAbsoluteTimeGetCurrent();

         // ...

    }

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

         dispatch_async(dispatch_get_main_queue(), ^{

            NSLog(@"Launched in %f sec", CFAbsoluteTimeGetCurrent() - StartTime);

         });

         // ...

    }

    这是通常的打印方法。然而,从main 开始启动有太多的不可控因素。

    iOS App启动过程

    • 链接并加载Framework和static lib

    • UIKit初始化

    • 应用程序callback

    • 第一个Core Animation transaction

    其中 framework的加载过程我们难以控制,而初始化字体、状态栏、user defaults、main nib等的过程,我们也不需要关心。当然,你可以做的是保持它们尽可能小,没有冗余。

    所以,还有一种做法是记录你在 didFinishLaunchingWithOptions 中所耗费的时间。在这里,你可能初始化了许多不必须的第三方库,做了几次网络请求。而这些,我们都可以 lazyLoad,让程序尽快地冷启动。

    帧率等

    如何优雅地显示帧率标签?

     

    在 QuartzCore.framework里,有一个 CADisplayLink 类。系统在渲染每一帧的时候,都会调用一次CADisplayLink中的 selector, 它有点像一个定时器类,默认为一秒调用60次。

    帧率大部分时间都稳定在 60fps,然而有两种情况下会导致它不能以每秒60次的频率调用回调方法。

    • CPU忙于其他计算,无暇执行屏幕绘制动作。

    • 执行回调方法所用时间大于重绘每帧的间隔时间。

    我们可以利用这个类的特性,写出如下代码来实时显示一个 屏幕帧率 的标签。

    CADisplayLink displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(tick:)];  

    [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];

    - (void)tick:(CADisplayLink *)displayLink {

    //...

    self.numberOfFrames++;

    NSTimeInterval delta = displayLink.timestamp - self.lastTimestamp;

        

        // Less than one second

        if (delta < 1) {

            return;

        }

        

        CGFloat fps = self.numberOfFrames / delta;

        

        //Update label

        ....

    }

    这个fps 的数值,便是每秒帧数。实测当下拉刷新,或加载List过快时,都会往下掉得很厉害。而这便是性能瓶颈所在的关键点。

  • 相关阅读:
    HDU4529 郑厂长系列故事——N骑士问题 —— 状压DP
    POJ1185 炮兵阵地 —— 状压DP
    BZOJ1415 聪聪和可可 —— 期望 记忆化搜索
    TopCoder SRM420 Div1 RedIsGood —— 期望
    LightOJ
    LightOJ
    后缀数组小结
    URAL
    POJ3581 Sequence —— 后缀数组
    hdu 5269 ZYB loves Xor I
  • 原文地址:https://www.cnblogs.com/fengmin/p/5968434.html
Copyright © 2011-2022 走看看