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

    iOS专项总结

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

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

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

      点击图片可放大缩小。

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

    关于 analyze

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

    image

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

    • 比如初始化赋值未被使用过。
    • 比如未调用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图片版本等。

    image

    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工具:

    image

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

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

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

    Time Profiler

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

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

    image

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

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

    有个循环在判断时候使用了枚举类型,但由于枚举类型获取不到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,让程序尽快地冷启动。

    帧率等

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

    image

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

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

    1. CPU忙于其他计算,无暇执行屏幕绘制动作。
    2. 执行回调方法所用时间大于重绘每帧的间隔时间。

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

    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过快时,都会往下掉得很厉害。而这便是性能瓶颈所在的关键点。

     本文系冰洁原创,遵循 署名-非商业性知识共享进行许可. 转载请在文章开头显眼处注明注明作者和出处 【冰洁】http://www.bingjie.me

  • 相关阅读:
    JSP(7)—EL和JSTL
    JSP(6)—JavaBean及案例
    JSP(5)—Session的创建以及简单使用
    JSP(4)—Cookie创建及简单案例(自动登录)
    JSP(3)—Cookie和Session
    JSP(2)—绝对路径与相对路径、配置Servlet与Servlet注解
    JSP(1)—基础知识
    Dockerfile RUN mkdir xxx 的时候报了Permission denied
    摘抄:Solr和ElasticSearch的区别
    POST请求反向代理设置
  • 原文地址:https://www.cnblogs.com/oc-bowen/p/5970274.html
Copyright © 2011-2022 走看看