以前也了解过oclint,看了一下大概的相关规则,里面的自定义规则用的很少.
这次有空,详细的记录一下对一个工程的OCLint之旅.
然后针对OC的代码添加若干自定义规则.
当前软硬件环境:
Xcode 9.2
macOS High Sierra Version 10.13.4
Xcode工程结构
Xcode工程目录
相关目录的解释
BDSSSDK: SDK代码路径
BuildScript: 放置一些脚本文件,目前有自动生成framework脚本和oclint脚本(这篇文章的重点)
framework:可以先忽略,跟此篇文章没有关系
oclint_result:oclint分析后的结果存放的路径
其他的是iOS工程引用pod使用到的.
原始工程编译
TDSSAppOCLint.sh文件
myworkspace=../TDSSApp.xcworkspace # 替换workspace的名字
myscheme=TDSSApp # 替换scheme的名字
DATE=$(date +%Y-%m-%d-%H-%M-%S)
FILE_NAME=../oclint_result/oclint_result_$DATE.html
xcodebuild -workspace $myworkspace -scheme $myscheme clean&&
xcodebuild -workspace $myworkspace -scheme $myscheme \
-configuration Debug \
| xcpretty -r json-compilation-database -o compile_commands.json&&
oclint-json-compilation-database -e Pods -- \
-report-type html -o $FILE_NAME \
-rc LONG_LINE=200 \
-max-priority-1=100000 \
-max-priority-2=100000 \
-max-priority-3=100000; \
rm compile_commands.json;
if [ -f ./$FILE_NAME ]; then echo '-----分析完毕-----'
else echo "-----分析失败-----"; fi
执行方式
方法一: cd到BuildScript, 然后执行命令:
bash TDSSAppOCLint.sh
方法二:添加Aggregate 的Target(例如名字为oclint)
添加成功后,添加Run Script
RunScript里的内容:
script_file_name=TDSSAppOCLint.sh
script_dir=${SRCROOT%/*}/BuildScript # 坑爹啊 */
script_path=$script_dir/$script_file_name
# 获取目录的相关权限
chmod +x $script_dir
# 获取文件的相关权限
chmod 764 $script_path
# 调用sh脚本文件
bash $script_path
echo "run xcode script end"
用xcode选择oclint target后直接编译,对于当前我的项目出现如下的结果:
只要出现分析完毕,就表示成功产生oclint报告了,其他的错误可以先不用管.
注意一点的是:分析完毕前出现 Build Succeeded, 不会马上出现分析完毕,估计要等一会儿,这个时候一定不要着急,等几分钟后就会出现分析完毕.
报告分析
打开最新生产的一个oclint报告
反正是各种oclint的Warning.
下面就是对各种Rule Name的进行分析
所有的规则列表:https://oclint-docs.readthedocs.io/en/stable/rules/index.html?highlight=unused%20method%20parameter
Priority 3
unused method parameter(UnusedMethodParameter)
顾名思义未使用的函数参数:
仔细定位了相关文件的相关代码,发现主要是代理,事件中未使用到传入参数,在iOS里这个太正常了,一般都会在代理中多传一些参数的.因此可以把这个警告给去掉,所以要修改.sh文件.
实际上需要区分的是,Xcode会自带对未使用变量的警告的(这个是跟参数还是有区别的).
去除这个警告:
在
-rc LONG_LINE=200 \
后面加上一行:
-disable-rule UnusedMethodParameter \
重新用方法二得到oclint结果:
看前面的图:
总共是969个,unused method parameter是266,重新运行后剩下969-266=703!
表明我们已经成功忽略了这种警告
short variable name(ShortVariableName)和long variable name(LongVariableName)
这个过短(95个)一般在一些缩写中用到,例如:
i:循环变量的下标
vc:viewController
等等.
过长(175个),oc是一种自解释的语言,一般都是比较长的,
按照上述的方法,对这2个规则添加规则:
-disable-rule LongVariableName \
-disable-rule ShortVariableName \
编译oclint:
预期的结果是:703-95-175=433个.
long line(LongLine)
-rc LONG_LINE=200
原本默认的是100,但是感觉OC的语言特点,我又比较喜欢在调用函数时写成一行,而不是写成多行,所以改成了200,但是感觉还是不够,我决定给他设置500
-rc LONG_LINE=500
再次编译得到的结果:
还有一个:
Xcode有自动折行功能,我感觉我还是直接disable 此 rule 吧
-disable-rule LongLine \
unnecessary default statement in covered switch statement(UnnecessaryDefaultStatement)
此rule是表明没有必要的default语法,当在switch中枚举了所有的value后,就没有必要使用default了(https://oclint-docs.readthedocs.io/en/stable/rules/convention.html#unnecessarydefaultstatement).
这里描述下一些个人有关枚举的处理办法.
在代码中尽量不出现纯数字来switch 或者 if (xx==1) 之类的写法,一般的写法都是把所有有可能的数字定义成枚举,例如如下:
typedef NS_ENUM(NSInteger, BDSSOrderStatus) {
BDSSOrderStatus_initilization = 0,
BDSSOrderStatus_auto = 5,
BDSSOrderStatus_published = 10,
BDSSOrderStatus_assign_duty = 13,
BDSSOrderStatus_accepted = 15,
BDSSOrderStatus_accepted_duty = 17,
BDSSOrderStatus_start = 20,
BDSSOrderStatus_try_end = 25,
BDSSOrderStatus_warning = 27,
BDSSOrderStatus_end = 30,
BDSSOrderStatus_end_by_sys = 31,
BDSSOrderStatus_scored = 35,
BDSSOrderStatus_canceled = 40,
BDSSOrderStatus_invalid = 43,
BDSSOrderStatus_reserve_2 = 45,
BDSSOrderStatus_reserve_3 = 50
};
NSString *NSStringFromBDSSOrderStatus(BDSSOrderStatus status);
而绝大部分这些数据都是从服务端传给客户端的,然后再Model层对字段进行转换类似于如下:
@property (nonatomic, readonly) BDSSOrderStatus orderStatus;
- (BDSSOrderStatus)orderStatus
{
return (BDSSOrderStatus)self.status;
}
这个时候就有可能遇到当服务器传status=51的时候(没有default的时候),在switch就会出现问题了,而且当时是在Debug编译模式下是没有问题,但到了Release编译模式就出现崩溃现象.最后发现是少了default,所以以后所有的switch都要在洗后写default,因此我也决定把这个规则给去掉.
-disable-rule UnnecessaryDefaultStatement
too few branches in switch statement(TooFewBranchesInSwitchStatement)
是switch分支太少,建议使用if语句,默认的是switch分支小于3个的时候,会出现警告,但是我喜欢使用switch,这样将来扩展也方便一下,因此有2种更改方法:
第一种是disable
第二种是把改成1(此篇文章我使用的方法):
-rc MINIMUM_CASES_IN_SWITCH=1
341-56=285:
unnecessary else statement(UnnecessaryElseStatement)
https://oclint-docs.readthedocs.io/en/stable/rules/redundant.html#unnecessaryelsestatement
那开始修改代码,修改成满足相关的写法(还好此项目只有15处,慢慢的改吧~~~)
为什么变成了268了? 我好想顺便修改了2个:ivar assignment outside accessors or init
ivar assignment outside accessors or init(AssignIvarOutsideAccessors)
https://oclint-docs.readthedocs.io/en/stable/rules/convention.html#assignivaroutsideaccessors
175个................
我感觉是有点历史遗留问题,我记得当时在MRC时代或者相关入门iOS书籍中都介绍了对属性的使用,那个时候都是用类似如下的代码:
_age = 1;
_nameLabel = [[UILabel alloc] init];
但是随着Xcode的升级与ARC的使用,我早已经改成了如下的规则:
尽量不要定义变量,能用属性就用属性,所有的属性前面访问都加self,尽量不要使用下划线开头的变量去设置属性,除非特殊情况.
所以导致还有这么多的warning警告.
又是一个大的工程!!!!!
改吧.
第一种
@property (nonatomic, strong) MSWeakTimer *timer;
- (void)endTimer {
self.haveNewMessage = NO;
if (_timer != nil) {
[_timer invalidate];
_timer = nil;
}
}
修改为:
@property (nonatomic, strong) MSWeakTimer *timer;
- (void)endTimer {
self.haveNewMessage = NO;
if (self.timer != nil) {
[self.timer invalidate];
self.timer = nil;
}
}
第二种
@interface BDSSGalleryViewController ()
{
BOOL _onceToken;
}
@end
- (void)viewDidLayoutSubviews
{
if (!_onceToken)
{
[self loadImage];
_onceToken = YES;
}
}
修改为:
@interface BDSSGalleryViewController ()
@property (nonatomic, assign) BOOL onceToken;
@end
- (void)viewDidLayoutSubviews
{
if (!self.onceToken) {
[self loadImage];
self.onceToken = YES;
}
}
第三种
// .h文件中
@property (nonatomic, assign, readonly) CGFloat isVisiable;
@property (nonatomic, assign, readonly) CGFloat keyboardHeight;
// .m文件中:
// 这里没有@synthesize isVisiable = _isVisiable;
@synthesize keyboardHeight = _keyboardHeight;
- (void)keyboardWillChangeFrame:(NSNotification *)notification
{
// other code
_isVisiable = endFrame.origin.y != [UIApplication sharedApplication].keyWindow.frame.size.height;
_keyboardHeight = _isVisiable? endFrame.size.height: 0;
// other code
}
修改为:
// .h文件中
@property (nonatomic, assign, readonly) CGFloat isVisiable;
@property (nonatomic, assign, readonly) CGFloat keyboardHeight;
// .m文件中:
@interface BDSSKitKeyboardInfo()
@property (nonatomic, assign) CGFloat isVisiable;
@property (nonatomic, assign) CGFloat keyboardHeight;
@end
- (void)keyboardWillChangeFrame:(NSNotification *)notification
{
// other code
self.isVisiable = endFrame.origin.y != [UIApplication sharedApplication].keyWindow.frame.size.height;
self.keyboardHeight = _isVisiable? endFrame.size.height: 0;
// other code
}
第四种
// .m文件中
@implementation BDSSKitDataRequest
{
NSMutableArray *_requstUserIdArray; //待请求池
BOOL _isRequesting;
}
- (void)afterReuquest:(NSArray *)userIds
{
_isRequesting = NO;
[_requstUserIdArray removeObjectsInArray:userIds];
[self request];
}
注意跟第二种的区别,这里是放在@implementation中了
修改为:
@interface BDSSKitDataRequest()
@property (nonatomic, strong) NSMutableArray *requstUserIdArray; // 待请求池
@property (nonatomic, assign) BOOL isRequesting;
@end
- (void)afterReuquest:(NSArray *)userIds
{
self.isRequesting = NO;
[self.requstUserIdArray removeObjectsInArray:userIds];
[self request];
}
初步改了一部分,现在的结果是:
parameter reassignment(ParameterReassignment)
https://oclint-docs.readthedocs.io/en/stable/rules/convention.html#parameterreassignment
修改传入的参数不是一个好习惯,所以需要消除这个warning,还好只有17个,消除的不会太累.......
修改后:
missing hash method(MissingHashMethod)这是Priority 1Warning
https://oclint-docs.readthedocs.io/en/stable/rules/cocoa.html#missinghashmethod
赶紧修复
useless parentheses(UselessParentheses)
https://oclint-docs.readthedocs.io/en/stable/rules/redundant.html#uselessparentheses
多余的括号的意思
样例一:(会产生warning)
CGFloat selfProtraitOriginX = (cellWidth - self.cellPaddingToAvatar.x - protraitImageWidth);
样例二:(会产生warning)
frame.origin.x = MAX(frame.origin.x, CGRectGetMinX(contentFrame));
样例三:(不会产生warning)
frame.size.height = MIN(frame.size.height, maxSize.height);
样例一可以修改,但是样例二怎么修改??
define MIN(A,B) __NSMIN_IMPL__(A,B,__COUNTER__)
所以直接忽略吧.
-disable-rule UselessParentheses
最新的,当前的sh文件:
myworkspace=../TDSSApp.xcworkspace # 替换workspace的名字
myscheme=TDSSApp # 替换scheme的名字
DATE=$(date +%Y-%m-%d-%H-%M-%S)
FILE_NAME=../oclint_result/oclint_result_$DATE.html
xcodebuild -workspace $myworkspace -scheme $myscheme clean&&
xcodebuild -workspace $myworkspace -scheme $myscheme \
-configuration Debug \
| xcpretty -r json-compilation-database -o compile_commands.json&&
oclint-json-compilation-database -e Pods -- \
-report-type html -o $FILE_NAME \
-disable-rule UnusedMethodParameter \
-disable-rule LongVariableName \
-disable-rule ShortVariableName \
-disable-rule UnnecessaryDefaultStatement \
-disable-rule UselessParentheses \
-rc MINIMUM_CASES_IN_SWITCH=1 \
-disable-rule LongLine \
-max-priority-1=100000 \
-max-priority-2=100000 \
-max-priority-3=100000; \
rm compile_commands.json;
if [ -f ./$FILE_NAME ]; then echo '-----分析完毕-----'
else echo "-----分析失败-----"; fi