前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何通过静态分析提高iOS代码质量

如何通过静态分析提高iOS代码质量

原创
作者头像
会写bug的程序员
修改2020-05-26 17:22:40
2K0
修改2020-05-26 17:22:40
举报
文章被收录于专栏:iOS面试iOS面试

随着项目的扩大,依靠人工codereview来保证项目的质量,越来越不现实,这时就有必要借助于一种自动化的代码审查工具:**程序静态分析**。

程序静态分析(Program Static Analysis)是指在不运行代码的方式下,通过词法分析、语法分析、控制流、数据流分析等技术对程序代码进行扫描,验证代码是否满足规范性、安全性、可靠性、可维护性等指标的一种代码分析技术。(来自百度百科)

词法分析,语法分析等工作是由编译器进行的,所以对iOS项目为了完成静态分析,我们需要借助于编译器。对于OC语言的静态分析可以完全通过Clang,对于Swift的静态分析除了Clange还需要借助于SourceKit

Swift语言对应的静态分析工具是SwiftLint,OC语言对应的静态分析工具有Infer和OCLitn。以下会是对各个静态分析工具的安装和使用做一个介绍。

SwiftLint

对于Swift项目的静态分析可以使用SwiftLint。SwiftLint 是一个用于强制检查 Swift 代码风格和规定的一个工具。它的实现是 Hook 了 Clang 和 SourceKit 从而能够使用 AST 来表示源代码文件的更多精确结果。Clange我们了解了,那SourceKit是干什么用的?

SourceKit包含在Swift项目的主仓库,它是一套工具集,支持Swift的大多数源代码操作特性:源代码解析、语法突出显示、排版、自动完成、跨语言头生成等工作。

安装

安装有两种方式,任选其一: **方式一:通过Homebrew**

代码语言:txt
复制
$ brew install swiftlint

这种是全局安装,各个应用都可以使用。 **方式二:通过CocoaPods**

代码语言:txt
复制
pod 'SwiftLint', :configurations => ['Debug']

这种方式相当于把SwiftLint作为一个三方库集成进了项目,因为它只是调试工具,所以我们应该将其指定为仅Debug环境下生效。

集成进Xcode

我们需要在项目中的Build Phases,添加一个Run Script Phase。如果是通过homebrew安装的,你的脚本应该是这样的。

代码语言:txt
复制
if which swiftlint >/dev/null; then

  swiftlint

else

  echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint"

fi

如果是通过cocoapods安装的,你得脚本应该是这样的:

代码语言:txt
复制
"${PODS\_ROOT}/SwiftLint/swiftlint"

运行SwiftLint

键入CMD + B编译项目,在编译完后会运行我们刚才加入的脚本,之后我们就能看到项目中大片的警告信息。有时候build信息并不能填入项目代码中,我们可以在编译的log日志里查看。

定制

SwiftLint规则太多了,如果我们不想执行某一规则,或者想要滤掉对Pods库的分析,我们可以对SwfitLint进行配置。

在项目根目录新建一个.swiftlint.yml文件,然后填入如下内容:

代码语言:txt
复制
disabled\_rules: # rule identifiers to exclude from running

  - colon

  - trailing\_whitespace

  - vertical\_whitespace

  - function\_body\_length

opt\_in\_rules: # some rules are only opt-in

  - empty\_count

  # Find all the available rules by running:

  # swiftlint rules

included: # paths to include during linting. `--path` is ignored if present.

  - Source

excluded: # paths to ignore during linting. Takes precedence over `included`.

  - Carthage

  - Pods

  - Source/ExcludedFolde

  - Source/ExcludedFile.swift

  - Source/\*/ExcludedFile.swift # Exclude files with a wildcard

analyzer\_rules: # Rules run by `swiftlint analyze` (experimental)

  - explicit\_self



# configurable rules can be customized from this configuration file

# binary rules can set their severity level

force\_cast: warning # implicitly

force\_try:

  severity: warning # explicitly

# rules that have both warning and error levels, can set just the warning level

# implicitly

line\_length: 110

# they can set both implicitly with an array

type\_body\_length:

  - 300 # warning

  - 400 # erro

# or they can set both explicitly

file\_length:

  warning: 500

  error: 1200

# naming rules can set warnings/errors for min\_length and max\_length

# additionally they can set excluded names

type\_name:

  min\_length: 4 # only warning

  max\_length: # warning and erro

    warning: 40

    error: 50

  excluded: iPhone # excluded via string

  allowed\_symbols: ["\_"] # these are allowed in type names

identifier\_name:

  min\_length: # only min\_length

    error: 4 # only erro

  excluded: # excluded via string array

    - id

    - URL

    - GlobalAPIKey

reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji, sonarqube, markdown)

一条rules提示如下,其对应的rules名就是function\_body\_length

代码语言:txt
复制
! Function Body Length Violation: Function body should span 40 lines or less excluding comments and whitespace: currently spans 43 lines (function\_body\_length)

disabled\_rules下填入我们不想遵循的规则。

excluded设置我们想跳过检查的目录,Carthage、Pod、SubModule这些一般可以过滤掉。

其他的一些像是文件长度(file_length),类型名长度(type_name),我们可以通过设置具体的数值来调节。

另外SwiftLint也支持自定义规则,我们可以根据自己的需求,定义自己的rule

生成报告

如果我们想将此次分析生成一份报告,也是可以的(该命令是通过homebrew安装的swiftlint):

代码语言:txt
复制
# reporter type (xcode, json, csv, checkstyle, junit, html, emoji, sonarqube, markdown)

$ swiftlint lint --reporter html > swiftlint.html

xcodebuild

xcodebuild是xcode内置的编译命令,我们可以用它来编译打包我们的iOS项目,接下来介绍的Infer和OCLint都是基于xcodebuild的编译产物进行分析的,所以有必要简单介绍一下它。

一般编译一个项目,我们需要指定项目名,configuration,scheme,sdk等信息以下是几个简单的命令及说明。

代码语言:txt
复制
# 不带pod的项目,target名为TargetName,在Debug下,指定模拟器sdk环境进行编译

xcodebuild -target TargetName -configuration Debug -sdk iphonesimulato

# 带pod的项目,workspace名为TargetName.xcworkspace,在Release下,scheme为TargetName,指定真机环境进行编译。不指定模拟器环境会验证证书

xcodebuild -workspace WorkspaceName.xcworkspace -scheme SchemeName Release

# 清楚项目的编译产物

xcodebuild -workspace WorkspaceName.xcworkspace -scheme SchemeName Release clean

**之后对xcodebuild命令的使用都需要将这些参数替换为自己项目的参数。**

Infe

Infer是Facebook开发的针对C、OC、Java语言的静态分析工具,它同时支持对iOS和Android应用的分析。对于Facebook内部的应用像是 Messenger、Instagram 和其他一些应用均是有它进行静态分析的。它主要检测隐含的问题,主要包括以下几条:

* 资源泄露,内存泄露

* 变量和参数的非空检测

* 循环引用

* 过早的nil操作

暂不支持自定义规则。

安装及使用

代码语言:txt
复制
$ brew install infe

运行infe

代码语言:txt
复制
$ cd projectDi

# 跳过对Pods的分析

$ infer run --skip-analysis-in-path Pods -- xcodebuild -workspace "Project.xcworkspace" -scheme "Scheme" -configuration Debug -sdk iphonesimulato

我们会得到一个infer-out的文件夹,里面是各种代码分析的文件,有txt,json等文件格式,当这样不方便查看,我们可以将其转成html格式:

代码语言:txt
复制
$ infer explore --html

点击trace,我们会看到该问题代码的上下文。

因为Infer默认是增量编译,只会分析变动的代码,如果我们想整体编译的话,需要clean一下项目:

代码语言:txt
复制
$ xcodebuild -workspace "Project.xcworkspace" -scheme "Scheme" -configuration Debug -sdk iphonesimulator clean

再次运行Infer去编译。

代码语言:txt
复制
$ infer run --skip-analysis-in-path Pods -- xcodebuild -workspace "Project.xcworkspace" -scheme "Scheme" -configuration Debug -sdk iphonesimulato

Infer的大致原理

Infer的静态分析主要分两个阶段:

**1、捕获阶段**

Infer 捕获编译命令,将文件翻译成 Infer 内部的中间语言。

这种翻译和编译类似,Infer 从编译过程获取信息,并进行翻译。这就是我们调用 Infer 时带上一个编译命令的原因了,比如: infer -- clang -c file.c, infer -- javac File.java。结果就是文件照常编译,同时被 Infer 翻译成中间语言,留作第二阶段处理。特别注意的就是,如果没有文件被编译,那么也没有任何文件会被分析。

Infer 把中间文件存储在结果文件夹中,一般来说,这个文件夹会在运行 infer 的目录下创建,命名是 infer-out/

**2、分析阶段**

在分析阶段,Infer 分析 infer-out/ 下的所有文件。分析时,会单独分析每个方法和函数。

在分析一个函数的时候,如果发现错误,将会停止分析,但这不影响其他函数的继续分析。

所以你在检查问题的时候,修复输出的错误之后,需要继续运行 Infer 进行检查,知道确认所有问题都已经修复。

错误除了会显示在标准输出之外,还会输出到文件 infer-out/bug.txt 中,我们过滤这些问题,仅显示最有可能存在的。

在结果文件夹中(infer-out),同时还有一个 csv 文件 report.csv,这里包含了所有 Infer 产生的信息,包括:错误,警告和信息。

OCLint

OCLint是基于Clange Tooling编写的库,它支持扩展,检测的范围比Infer要大。不光是隐藏bug,一些代码规范性的问题,例如命名和函数复杂度也均在检测范围之内。

安装OCLint

OCLint一般通过Homebrew安装

代码语言:txt
复制
$ brew tap oclint/formulae   

$ brew install oclint

通过Hombrew安装的版本为0.13。

代码语言:txt
复制
$ oclint --version

LLVM (http://llvm.org/):

  LLVM version 5.0.0svn-r313528

  Optimized build.

  Default target: x86\_64-apple-darwin19.0.0

  Host CPU: skylake



OCLint (http://oclint.org/):

  OCLint version 0.13.

  Built Sep 18 2017 (08:58:40).

我分别用Xcode11在两个项目上运行过OCLint,一个实例项目可以正常运行,另一个复杂的项目却运行失败,报如下错误:

代码语言:txt
复制
1 error generated

1 error generated

...

oclint: error: cannot open report output file ..../onlintReport.html

我并不清楚原因,如果你想试试0.13能否使用的话,直接跳到安装xcpretty。如果你也遇到了这个问题,可以回来安装oclint0.15版本。

OCLint0.15

我在oclint issuse #547这里找到了这个问题和对应的解决方案。

我们需要更新oclint至0.15版本。brew上的最新版本是0.13,github上的最新版本是0.15。我下载github上的release0.15版本,但是这个包并不是编译过的,不清楚是不是官方自己搞错了,只能手动编译了。因为编译要下载llvm和clange,这两个包较大,所以我将编译过后的包直接传到了这里CodeChecker

如果不关心编译过程,可以下载编译好的包,跳到设置环境变量那一步。

**编译OCLint**

1、安装CMakeNinja这两个编译工具

代码语言:txt
复制
$ brew install cmake ninja

2、clone OCLint项目

代码语言:txt
复制
$ git clone https://github.com/oclint/oclint

3、进入oclint-scripts目录,执行make命令

代码语言:txt
复制
$ ./make

成功之后会出现build文件夹,里面有个oclint-release就是编译成功的oclint工具。

**设置oclint工具的环境变量**

设置环境变量的目的是为了我们能够快捷访问。然后我们需要配置PATH环境变量,注意OCLint_PATH的路径为你存放oclint-release的路径。将其添加到.zshrc,或者.bash\_profile文件末尾:

代码语言:txt
复制
OCLint\_PATH=/Users/zhangferry/oclint/build/oclint-release

export PATH=$OCLint\_PATH/bin:$PATH

执行source .zshrc,刷新环境变量,然后验证oclint是否安装成功:

代码语言:txt
复制
$ oclint --version

OCLint (http://oclint.org/):

OCLint version 0.15.

Built May 19 2020 (11:48:49).

出现这个介绍就说明我们已经完成了安装。

安装xcpretty

xcpretty是一个格式化xcodebuild输出内容的脚本工具,oclint的解析依赖于它的输出。它的安装方式为:

代码语言:txt
复制
$ gem install xcpretty

OCLint的使用

在使用OCLint之前还需要一些准备工作,需要将编译项COMPILER\_INDEX\_STORE\_ENABLE设置为NO。

* 将 Project 和 Targets 中 Building Settings 下的 COMPILER\_INDEX\_STORE\_ENABLE 设置为 **NO**

* 在 podfile 中 **target 'target' do 前面**添加下面的脚本,将各个pod的编译配置也改为此选项

代码语言:txt
复制
post\_install do |installer|

  installer.pods\_project.targets.each do |target|

      target.build\_configurations.each do |config|

          config.build\_settings['COMPILER\_INDEX\_STORE\_ENABLE'] = "NO"

      end

  end

end
使用方式

1、进入项目根目录,运行如下脚本:

代码语言:txt
复制
$ xcodebuild -workspace ProjectName.xcworkspace -scheme ProjectScheme -configuration Debug -sdk iphonesimulator | xcpretty -r json-compilation-database -o compile\_commands.json

会将xcodebuild编译过程中的一些信息记录成一个文件compile\_commands.json,如果我们在项目根目录看到了该文件,且里面是有内容的,证明我们完成了第一步。

2、我们将这个json文件转成方便查看的html,过滤掉对Pods文件的分析,为了防止行数上限,我们加上行数的限制:

代码语言:txt
复制
$ oclint-json-compilation-database -e Pods -- -report-type html -o oclintReport.html -rc LONG\_LINE=9999 -max-priority-1=9999 -max-priority-2=9999 -max-priority-3=9999

最终会产生一个oclintReport.html文件。

OCLint支持自定义规则,因为其本身规则已经很丰富了,自定义规则的需求应该很小,也就没有尝试。

**封装脚本**

OCLint跟Infer一样都是通过运行几个脚本语言进行执行的,我们可以将这几个命令封装成一个脚本文件,以OCLint为例,Infer也类似:

代码语言:txt
复制
#!/bin/bash

# mark sure you had install the oclint and xcpretty



# You need to replace these values with your own project configuration

workspace\_name="WorkSpaceName.xcworkspace"

scheme\_name="SchemeName"



# remove history

rm compile\_commands.json

rm oclint\_result.xml

# clean project

# -sdk iphonesimulator means run simulato

xcodebuild -workspace $workspace\_name -scheme $scheme\_name -configuration Debug -sdk iphonesimulator clean || (echo "command failed"; exit 1);



# export compile\_commands.json

xcodebuild -workspace $workspace\_name -scheme $scheme\_name -configuration Debug -sdk iphonesimulator \

| xcpretty -r json-compilation-database -o compile\_commands.json \

|| (echo "command failed"; exit 1);



# export report html

# you can run `oclint -help` to see all USAGE

oclint-json-compilation-database -e Pods -- -report-type html -o oclintReport.html \

-disable-rule ShortVariableName \

-rc LONG\_LINE=1000 \

|| (echo "command failed"; exit 1);



open -a "/Applications/Safari.app" oclintReport.html

oclint-json-compilation-database命令的几个参数说明:

-e 需要忽略分析的文件,这些文件的警告不会出现在报告中

-rc 需要覆盖的规则的阀值,这里可以自定义项目的阀值,默认阀值

-enable-rule 支持的规则,默认是oclint提供的都支持,可以组合-disable-rule来过滤掉一些规则 规则列表

-disable-rule 需要忽略的规则,根据项目需求设置

在Xcode中使用OCLint

因为OCLint提供了xcode格式的输出样式,所以我们可以将它作为一个脚本放在Xcode中。

1、在项目的 TARGETS 下面,点击下方的 "+" ,选择 cross-platform 下面的 Aggregate。输入名字,这里命名为 OCLint

2、选中该Target,进入Build Phases,添加Run Script,写入下面脚本:

代码语言:txt
复制
# Type a script or drag a script file from your workspace to insert its path.

# 内置变量

cd ${SRCROOT}

xcodebuild clean 

xcodebuild | xcpretty -r json-compilation-database

oclint-json-compilation-database -e Pods -- -report-type xcode

可以看出该脚本跟上面的脚本一样,只不过 将oclint-json-compilation-database命令的-report-typehtml改为了xcode。而OCLint作为一个target本身就运行在特定的环境下,所以xcodebuild可以省去配置参数。

3、通过CMD + B我们编译一下项目,执行脚本任务,会得到能够定位到代码的warning信息:

总结

以下是对这几种静态分析方案的对比,我们可以根据需求选择适合自己的静态分析方案。

| | SwiftLint | Infer | OCLint |

| --- | --- | --- | --- |

| 支持语言 | Swift | C、C++、OC、Java | C、C++、OC |

| 易用性 | 简单 | 较简单 | 较简单 |

| 能否集成进Xcode | 可以 | 不能集成进xcode | 可以 |

| 自带规则丰富度 | 较多,包含代码规范 | 相对较少,主要检测潜在问题 | 较多,包含代码规范 |

| 规则扩展性 | 可以 | 不可以 | 可以 |

参考

OCLint 实现 Code Review - 给你的代码提提质量

Using OCLint in Xcode

Infer 的工作机制

LLVM & Clang 入门

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • SwiftLint
    • 安装
      • 集成进Xcode
        • 运行SwiftLint
          • 定制
            • 生成报告
            • xcodebuild
            • Infe
              • 安装及使用
                • Infer的大致原理
                  • 安装OCLint
                  • OCLint0.15
              • OCLint
                • 安装xcpretty
                  • OCLint的使用
                    • 使用方式
                    • 在Xcode中使用OCLint
                • 总结
                • 参考
                相关产品与服务
                腾讯云代码分析
                腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档