美文网首页iOS开发技术
Xcode警告常见问题汇总

Xcode警告常见问题汇总

作者: Nine_suns | 来源:发表于2021-08-10 22:57 被阅读0次

    背景

    近期公司的项目开启了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+SwiftInferOCLintSwiftLint等工具,定期的分析项目的代码质量并解决相关风险代码问题。

    相关文章

      网友评论

        本文标题:Xcode警告常见问题汇总

        本文链接:https://www.haomeiwen.com/subject/vjwpvltx.html