Xcode 代码注释自动缩进插件:开发教程及源码

jopen 9年前

Xcode 代码注释自动缩进插件:开发教程及源码

自从公司启用代码review以来,每个开发者的代码风格渐渐保持一致了,看到规范统一的代码就是觉得比较舒服。

不过Xcode的代码注释功能一直用着很别扭,比如下面的例子:

//注释前:  - (void)viewDidLoad  {      [super viewDidLoad];      self.view.backgroundColor = [UIColor whiteColor];  }  //注释后:  - (void)viewDidLoad  {      [super viewDidLoad];  //    self.view.backgroundColor = [UIColor whiteColor];  }  //注释前:  - (void)viewDidLoad  {      [super viewDidLoad];         self.view.backgroundColor = [UIColor whiteColor];  }     //注释后:  - (void)viewDidLoad  {      [super viewDidLoad];     //    self.view.backgroundColor = [UIColor whiteColor];  }

可以看到,Xcode只是在行开头加上注释符,注释后的代码缩进对不齐,很难看。这对患有神经感官歇斯底里毛细血管穿梭图鲁西斯症候群(简称强迫症)的人来说,是一件非常痛苦的事情。

在stackoverflow搜了一下,发现有人也提了这个问题,然后有个人回答说:

  1. 你先按command + [把代码往左缩进到最前面

  2. 再按command + /注释代码

  3. 最后按command + ]把代码往右缩进

结果这条回答被采纳为最差答案。

所以我只能自己写个插件来让注释自动缩进了,首先新建一个Xcode插件工程,比较详细的创建过程可以见《 Xcode插件AllTargets开发教程 》。

一开始要先整理一下写插件的思路,我们使用快捷键command + /的时候,Xcode肯定会调用一个方法来注释代码,这个方法实现的功能应该是在代码行最前面加上//注释符。

而当我们写的代码有缩进的话,缩进的地方都是空格,所以我们可以hook注释的方法,把注释符改为插入到第一个非空格字符前就行了。

接下来就开始查找代码注释的方法了,首先在Xcode的私有类头文件里搜索comment,可以发现一个比较可疑的方法:- (void)commentAndUncommentCurrentLines:(id)arg1;,hook这个方法,把参数arg1打印出来,参考代码如下:

#import "DVTSourceTextView+Hook.h"  @implementation DVTSourceTextView (Hook)  + (void)hook  {      [self jr_swizzleMethod:@selector(commentAndUncommentCurrentLines:)                  withMethod:@selector(hook_commentAndUncommentCurrentLines:)                       error:nil];  }  - (void)hook_commentAndUncommentCurrentLines:(id)arg1  {      NSLog(@"%@", arg1);      [self hook_commentAndUncommentCurrentLines:arg1];  }  @end  #import "DVTSourceTextView+Hook.h"     @implementation DVTSourceTextView (Hook)     + (void)hook  {      [self jr_swizzleMethod:@selector(commentAndUncommentCurrentLines:)                  withMethod:@selector(hook_commentAndUncommentCurrentLines:)                       error:nil];  }     - (void)hook_commentAndUncommentCurrentLines:(id)arg1  {      NSLog(@"%@", arg1);      [self hook_commentAndUncommentCurrentLines:arg1];  }     @end

使用command + /对代码进行注释和反注释,输出结果为:

由此可知,使用快捷键注释,实际上是调用了Xcode菜单栏点击的方法。而当我们注释代码时,会有log打印,也说明找对了方法。

commentAndUncommentCurrentLines:这个方法位于DVTSourceTextView类中,而这个类位于DVTKit.framework里,接下来打开反汇编软件Hopper Disassembler,把DVTKit.framework拖进去进行反汇编。

搜索并选中commentAndUncommentCurrentLines:方法,按alt + enter生成伪代码,由于代码太长所以只贴出关键代码:

void -[DVTSourceTextView commentAndUncommentCurrentLines:](void * self, void * _cmd, void * arg2)  {      //......      rax = (r12)(r15, @selector(stringByTogglingCommentsInLineRange:), rdx, r13);      //......  }  void -[DVTSourceTextView commentAndUncommentCurrentLines:](void * self, void * _cmd, void * arg2)  {      //......         rax = (r12)(r15, @selector(stringByTogglingCommentsInLineRange:), rdx, r13);         //......  }

由上面的代码可知,stringByTogglingCommentsInLineRange:方法返回了注释或反注释后的字符串,接着再搜索并查看该方法的伪代码:

void * -[DVTSourceLanguageService stringByTogglingCommentsInLineRange:](void * self, void * _cmd, struct _NSRange arg2)   {      //......      if (var_70 == 0x2) {          rbx = [[rdi stringByUncommentingString:r15] retain];      } else {          rbx = [[rdi stringByCommentingString:r15] retain];      }      //......  }  void * -[DVTSourceLanguageService stringByTogglingCommentsInLineRange:](void * self, void * _cmd, struct _NSRange arg2)   {      //......         if (var_70 == 0x2) {          rbx = [[rdi stringByUncommentingString:r15] retain];      } else {          rbx = [[rdi stringByCommentingString:r15] retain];      }         //......  }

同理,继续查看stringByCommentingString:方法的伪代码:

void * -[DVTSourceLanguageService stringByCommentingString:](void * self, void * _cmd, void * arg2)  {      rdx = arg2;      r15 = self;      r12 = [rdx retain];      r14 = *objc_msgSend;      rbx = [[r15 lineCommentPrefixes] retain];      r13 = [[rbx firstObject] retain];      [rbx release];      if ([r13 length] != 0x0) {              r15 = r13;      }      else {              r14 = *objc_msgSend;              var_30 = r12;              rbx = [[r15 blockCommentCircumfixes] retain];              r12 = [[rbx firstObject] retain];              r15 = *objc_release;              [rbx release];              rbx = r15;              r15 = [[r12 firstObject] retain];              [r13 release];              r13 = [[r12 lastObject] retain];              [r12 release];              if (r13 != 0x0) {                      r12 = [[var_30 stringByAppendingString:r13] retain];                      (rbx)(var_30, @selector(stringByAppendingString:));                      (rbx)(r13, @selector(stringByAppendingString:));              }              else {                      r12 = var_30;              }      }      rbx = [[r15 stringByAppendingString:r12] retain];      r14 = *objc_release;      [r12 release];      [r15 release];      rdi = rbx;      rax = [rdi autorelease];      return rax;  }  void * -[DVTSourceLanguageService stringByCommentingString:](void * self, void * _cmd, void * arg2)  {      rdx = arg2;      r15 = self;      r12 = [rdx retain];      r14 = *objc_msgSend;      rbx = [[r15 lineCommentPrefixes] retain];      r13 = [[rbx firstObject] retain];      [rbx release];      if ([r13 length] != 0x0) {              r15 = r13;      }      else {              r14 = *objc_msgSend;              var_30 = r12;              rbx = [[r15 blockCommentCircumfixes] retain];              r12 = [[rbx firstObject] retain];              r15 = *objc_release;              [rbx release];              rbx = r15;              r15 = [[r12 firstObject] retain];              [r13 release];              r13 = [[r12 lastObject] retain];              [r12 release];              if (r13 != 0x0) {                      r12 = [[var_30 stringByAppendingString:r13] retain];                      (rbx)(var_30, @selector(stringByAppendingString:));                      (rbx)(r13, @selector(stringByAppendingString:));              }              else {                      r12 = var_30;              }      }      rbx = [[r15 stringByAppendingString:r12] retain];      r14 = *objc_release;      [r12 release];      [r15 release];      rdi = rbx;      rax = [rdi autorelease];      return rax;  }

这个方法的作用是把待注释的代码加上注释符进行注释,这也是Xcode注释代码的具体实现方法,将伪代码翻译成OC代码为:

- (NSString *)stringByCommentingString:(NSString *)commentingString  {      NSString *lineCommentPrefix = [self.lineCommentPrefixes firstObject];      if (lineCommentPrefix.length == 0) {          // 使用 `/* */` 注释块          NSArray *blockCommentCircumfix = [self.blockCommentCircumfixes firstObject];          lineCommentPrefix = [blockCommentCircumfix firstObject];          NSString *lineCommentSuffix = [blockCommentCircumfix lastObject];          if (lineCommentSuffix) {              commentingString = [commentingString stringByAppendingString:lineCommentSuffix];          }      }      //在待注释的代码最前面加上注释符      return [lineCommentPrefix stringByAppendingString:commentingString];  }  - (NSString *)stringByCommentingString:(NSString *)commentingString  {      NSString *lineCommentPrefix = [self.lineCommentPrefixes firstObject];         if (lineCommentPrefix.length == 0) {             // 使用 `/* */` 注释块          NSArray *blockCommentCircumfix = [self.blockCommentCircumfixes firstObject];          lineCommentPrefix = [blockCommentCircumfix firstObject];             NSString *lineCommentSuffix = [blockCommentCircumfix lastObject];          if (lineCommentSuffix) {              commentingString = [commentingString stringByAppendingString:lineCommentSuffix];          }      }         //在待注释的代码最前面加上注释符      return [lineCommentPrefix stringByAppendingString:commentingString];  }

因此我们可以hook这个方法,在第一个非空格字符的位置插入注释符。

如果某一行代码是空行的话,就不需要注释了,我看了大部分的编辑器都是这么处理的。

参考代码如下:

- (NSString *)hook_stringByCommentingString:(NSString *)commentingString  {      NSString *lineCommentPrefix = [self.lineCommentPrefixes firstObject];      if (lineCommentPrefix.length == 0) {          // 使用 `/* */` 注释块          NSArray *blockCommentCircumfix = [self.blockCommentCircumfixes firstObject];          lineCommentPrefix = [blockCommentCircumfix firstObject];          NSString *lineCommentSuffix = [blockCommentCircumfix lastObject];          if (lineCommentSuffix) {              commentingString = [commentingString stringByAppendingString:lineCommentSuffix];          }      }      // 判断代码是否含有非空格字符      NSRange range = [commentingString rangeOfString:@"[^\\s]" options:NSRegularExpressionSearch];      if (range.location == NSNotFound) {          // 如果没有非空格字符,就不注释          return commentingString;      }      // 在第一个非空格字符的位置插入注释符      NSMutableString *mutableString = [commentingString mutableCopy];      [mutableString insertString:lineCommentPrefix atIndex:range.location];      return [mutableString copy];  }  - (NSString *)hook_stringByCommentingString:(NSString *)commentingString  {      NSString *lineCommentPrefix = [self.lineCommentPrefixes firstObject];         if (lineCommentPrefix.length == 0) {             // 使用 `/* */` 注释块          NSArray *blockCommentCircumfix = [self.blockCommentCircumfixes firstObject];          lineCommentPrefix = [blockCommentCircumfix firstObject];             NSString *lineCommentSuffix = [blockCommentCircumfix lastObject];          if (lineCommentSuffix) {              commentingString = [commentingString stringByAppendingString:lineCommentSuffix];          }      }         // 判断代码是否含有非空格字符      NSRange range = [commentingString rangeOfString:@"[^\\s]" options:NSRegularExpressionSearch];      if (range.location == NSNotFound) {          // 如果没有非空格字符,就不注释          return commentingString;      }         // 在第一个非空格字符的位置插入注释符      NSMutableString *mutableString = [commentingString mutableCopy];      [mutableString insertString:lineCommentPrefix atIndex:range.location];      return [mutableString copy];  }

插件下载地址为: https://github.com/poboke/IndentComments

安装插件后,使用效果如下所示:

Xcode 代码注释自动缩进插件:开发教程及源码

来自: http://www.cocoachina.com/ios/20151229/14806.html