Xcode警告常见问题汇总

背景

近期公司的项目开启了SwiftObjC的混编,随之也将部分Xcode的警告选项做了开启。开启后发现多出了很多的警告问题,大部分是代码不规范引起的低级的错误。于是在团队里成立了一个清理小组,专门立项对这些警告问题进行清理。这里总结一下我们在清理过程中发现的一些常见问题。

Warning2Error

Xcode的警告可以通过设置编译选项变为编译错误,参考文章。
具体操作方式如下:

-Werror: Turn warnings into errors.
-Werror=foo: Turn warning "foo" into an error.
-Wno-error=foo: Turn warning "foo" into an warning even if -Werror is specified.
-Wfoo: Enable warning foo
-Wno-foo: Disable warning foo
-w: Disable all warnings.

我们依靠人工的CodeReview很难做到没有遗漏,对于一些低级代码错误,还是需要借助自动化的方式来做,最好是在开发阶段实时报错提示,以此来避免出现低级失误,提高代码质量,守住研发底线。

所以这里先介绍将警告转换为错误的方法,在实际项目中对一些我们认为不应该出现的警告代码,设置为编译错误,减少这些风险代码的产生。这样的设置越早开启越好,在项目变大之后改起来成本会很高。

对于一些例外的情况,比如有的场景就是需要用一些会触发警告的代码,可以通过如下代码将Clang的诊断关闭:

#pragma clang diagnostic push  
#pragma clang diagnostic ignored "-Wunused-variable"  
    // 你自己的代码  
#pragma clang diagnostic pop

常见问题

1. NS_DESIGNATED_INITIALIZER

警告
  • 1: Method override for the designated initializer of the superclass '-init' not found
  • 2: Convenience initializer missing a 'self' call to another initializer
  • 3: Convenience initializer should not invoke an initializer on 'super
Clang选项

-Wobjc-designated-initializers
传送门

原因

以上3个警告是由于使用NS_DESIGNATED_INITIALIZER不规范引起的。具体分析见这篇文章。

2. 方法形参命名重复

警告
  • Redefinition of method parameter 'x'
原因

原因比较简单,在实现方法时出现了相同名称的形参,触发场景如下:

- (void)callMethodWithA:(NSString *)a andB:(NSString *)a
{
  NSLog(@"%@", a);
}

这里有一点需要注意,在上述场景中,第二个变量a的值会被忽略,即优先使用顺序靠前的实参的值。

//这里实际打印的是first
[self callMethodWithA:@"first" andB:@"second"];

这种警告改起来也很简单,确保参数命名不同即可。作为开发人员出现这种错误是不应该的,但是在实际项目中我们的确看到有很多这种警告,这种低级错误应该编译报错,而不是简单的给个警告。(需要调研Clang是否支持将该种类型警告变为错误)。

3. 方法重复声明

警告
  • Multiple declarations of method 'x' found and ignored
Clang选项

-Wduplicate-method-match
传送门

原因

原因很直观,在类或者分类的声明中,出现了重复的方法名,触发场景如下:

@interface Warning : NSObject
- (void)callA;
- (void)callA;
@end
//或者分类中重复声明同一个方法
@interface Warning (Category)
- (void)callB;
- (void)callB;
@end

解决方案也简单,删除一个重复的命名即可,低级错误。

4. 无用变量

警告
  • unused variable A
Clang选项

-Wunused-variable
-Wunused-const-variable

传送门1
传送门2

原因

声明的变量未使用,这种情况多数是代码迭代,逻辑变更时,没有考虑到要删除不再使用的变量。

5. 不会执行的代码

警告
  • code will never be executed
Clang选项

-Wunreachable-code
传送门

原因

这个警告在我们项目中主要是在下面两个场景下触发:

//if 逻辑判断永远为false
if (a == b || c) {
  //原意是a == b || a ==c,c是某个大于0的整数,写法错误导致else内的代码得不到执行
}
else {
  // Warning
}

//方法逻辑不需要了,没有删除,为了省事直接前置return
- (void)someMethod
{
  return;
  //下面的代码逻辑不需要了
  int a = 1;
  NSLog(@"%d", a);
}
  • if 逻辑判断永远为false
  • 代码逻辑废弃,前置return

6. 方法有声明,没有实现

警告
  • method definition for A not found
Clang选项

-Wincomplete-implementation
传送门

原因

类的定义中声明了某个方法,但是并没有实现。一般在定义协议时会声明方法,由遵循该协议的类去实现具体的方法。但是在我们的实际项目中,常见的场景是定义了基类,声明了方法,却不提供默认的实现,而仅仅在头文件里加了注释,告诉使用者需要子类来实现。

@interface SomeBaseClass : NSObject
- (void)methodA; //由子类实现
@end

@implementation SomeBaseClass
//no mehtod implementation for methodA
@end

这样的用法有些不伦不类,既不是协议,也不是一个完整的类,很容易给使用者造成困扰。

7. 隐式强引用self

警告
  • block implicitly retains ‘self’; explicitly mention ‘self’ to indicate this is intended behavior
Clang选项

-Wimplicit-retain-self
传送门

原因

这个警告的常见出现场景是在block内部直接使用实例变量_instance这种方式:

@interface SomeClass : NSObject
@property (nonatomic, strong) NSString *name;
@end
@implementation SomeClass
- (void)someMehtod
{
  dispatch_async(dispatch_get_main_queue(), ^{
    _name = @"John"; //没有明确使用self,直接通过下划线访问
  });
}
@end

这种问题容易出现循环引用,需要block捕获self时,建议通过self->_name或者self.name的方式访问,不要直接访问_name,将你的意图通过代码明确的表达出来,提升代码的可读性及可维护性。

8. 协议的方法没有实现

警告
  • method A in protocol B not implemented
Clang选项

-Wprotocol
传送门

原因

遵循了协议B的某个类,没有实现协议B中声明的方法A。这个警告一般有两个场景会出现:

  • 没有实现协议中要求为@required的方法
  • 协议没有明确指定哪些方法是@optional的,默认情况下所有的方法都被认为是@required

这类警告要求我们在定义实现协议时,要规范化:即定义时要明确哪些方法是必需实现的,哪些方法是可选的;实现时要保证实现所有必需的方法。

协议毕竟只是一份声明,一些类可能声明了自己遵循某个协议,但是实际并没有实现相关协议方法。这种情况下协议方法的调用者在执行某个方法前,不能简单的通过conformsToProtocol来确保方法可调用,而是应该通过respondsToSelector的方式来确认方法可调用。

9. 类型不匹配

警告
incompatible-pointer-types
Clang选项

-Wincompatible-pointer-types
传送门

原因

这个警告是我们项目中发现最多的,原因很简单,就是变量类型不匹配,比如声明的UIImage *类型变量,接受的却是一个UIView *的返回值:

- (void)someMethod
{
  UIImage *imageView = [self getImageView];
}

- (UIView *)getImageView
{
  return [[UIView alloc] init];
}

在工程警告关闭的情况下,上述使用场景是不会报编译错误的,也不会有警告提示。开发人员在使用时没有意识到自己声明错类型了,依然将imageView当作UIView来使用,虽然在代码逻辑执行上没有什么问题,但是可读性、可维护性就相当差了。还有的案例如下:

@interface SomeClassA : NSObject
- (void)playWithVideoUrl:(NSString *)url;
@end
@implementation SomeClassA
- (void)playWithVideoUrl:(NSString *)url
{
 self.video.url = url;//video.url实际是一个NSURL类型的变量
 [self.video play];
}
@end

这里playWithVideoUrl对外声明的是需要传入一个NSString *类型的变量,但是内部实现时实际需要的是一个NSURL *的类型,之所以上述代码在测试阶段没有发现问题,是因为在外部使用者调用时,传入的也是一个NSURL *类型。简单来说错误的只有这个方法的声明,调用和实现两个部分都是按照预期进行的,这种错误给后续维护人员会带来很大的困扰。

我们的实际项目中关于NSArrayNSMutableArray来回赋值的类似例子有很多,一方面是对开发人员对类型的声明和实际传递不够关注;另一方面也是由于警告被关闭,对这类错误的容忍度很高,久而久之导致很多这种类型不匹配的问题出现,为后续进行警告清理带来很大的工作成本。所以强烈建议新的项目各种警告一定要开起来

总结

经过这次清理警告的过程,我认为一定要将警告重视起来,尤其是在团队协作的项目中,代码的质量也是团队的精神面貌的体现。总结起来有两点:

  • 严格的把低级错误,由警告设置为错误,在开发阶段就将问题暴露。
  • 在项目建立之初尽早的开启相关警告,避免问题积重难返。

最后也要建立起代码质量监控的手段,比如使用Sonar+Swift、Infer、OCLint、SwiftLint等工具,定期的分析项目的代码质量并解决相关风险代码问题。

你可能感兴趣的:(Xcode警告常见问题汇总)