## 命名规范 1.iOS命名原则 * 可读性高、可复用、易维护、可扩展 * 防止命名冲突(通过加前缀来保证) * 每个模块都要加上自己的前缀, 前缀在编程接口中非常重要, 可以区分软件的功能范畴并防止不同文件或者类之间命名发生冲突, 比如相册模块(PhotoGallery)的代码都以PG作为前缀: PGAlbumViewController, PGDataManager. * 可扩展则是要求写代码时要考虑后面的扩展需求, 这个属于架构层面的东东, 利用对应的设计模式来保证 2.iOS常量命令 1)对于常量的命名最好在前面加上字母k作为标记. 如: ``` static const NSTimeInterval kAnimationDuration = 0.3; ``` 2)定义作为NSDictionary或者Notification等的Key值字符串时加上const关键字, 以防止被修改. 如: ``` NSString *const UIApplicationDidEnterBackgroundNotification ``` 3)若常量作用域超出编译单元(实现文件), 需要在类外可见时, 使用extern关键字, 并加上该类名作为前缀. 如 ``` extern NSString *const PGThumbnailSize ``` <!--more--> 3.iOS枚举命令 枚举类型命名要加相关类名前缀并且枚举值命名要加枚举类型前缀. ``` typedef NS_ENUM(NSInteger, UIViewAnimationTransition) { UIViewAnimationTransitionNone, UIViewAnimationTransitionFlipFromLeft, UIViewAnimationTransitionFlipFromRight, UIViewAnimationTransitionCurlUp, UIViewAnimationTransitionCurlDown, }; ``` 4.iOS变量和对象命名 1)给一个对象命名时建议采用修饰+类型的方式 ``` titleLabel //表示标题的label, 是UILabel类型 confirmButton //表示确认的button, 是UIButton类型 ``` 2)对于BOOL类型, 应加上is前缀 ``` - (BOOL)isEqualToString:(NSString *)aString ``` 3)如果某方法返回非属性的 BOOL 值, 那么应根据其功能, 选用 has 或 is 当前缀, 如 ``` - (BOOL)hasPrefix:(NSString *)aString ``` 4)如果某个命名已经很明确了, 为了简洁可以省去类型名. 比如scores, 很明显是个array了, 就不必命名成scoreArray了 5.iOS命名常规错误 ``` UserFollowerTableViewController // 不推荐 UserFollowerListController // OK UserLikedTagListController // 不推荐 TagUserLikedListController // OK,把显示的对象放在第一位 ``` 6.iOS通知命名 这里学习iOS的命名方法: ``` NSApplicationDidBecomeActiveNotification NSWindowDidMiniaturizeNotification NSTextViewDidChangeSelectionNotification NSColorPanelColorDidChangeNotification ``` 7.类名、局部变量、类成员命名 * 类名采用大驼峰(UpperCamelCase) * 类成员、方法小驼峰(lowerCamelCase) * 局部变量大小写首选小驼峰,也可使用小写下划线的形式(snake_case) * C函数的命名用大驼峰 8.命名规范--函数命名 * 1)如果方法表示让对象执行一个动作,使用动词打头来命名,注意不要使用do,does这种多余的关键字,动词本身的暗示就足够了;动词打头的方法表示让对象执行一个动作 ``` - (void)invokeWithTarget:(id)target; - (void)selectTabViewItem:(NSTabViewItem *)tabViewItem; ``` * 2)如果方法是为了获取对象的一个属性值,直接用属性名称来命名这个方法,注意不要添加get或者其他的动词前缀 正确,使用属性名来命名方法 ``` - (NSSize)cellSize; ``` 错误,添加了多余的动词前缀 ``` - (NSSize)calcCellSize; - (NSSize)getCellSize; ``` * 3)对于有多个参数的方法,务必在每一个参数前都添加关键词,关键词应当清晰说明参数的作用 正确,保证每个参数都有关键词修饰 ``` - (void)sendAction:(SEL)aSelector toObject:(id)anObject forAllCells:(BOOL)flag; ``` 错误,遗漏关键词 ``` - (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag; ``` 正确 ``` - (id)viewWithTag:(NSInteger)aTag; ``` 错误,关键词的作用不清晰 ``` - (id)taggedView:(int)aTag; ``` * 4)不要用and来连接两个参数,通常and用来表示方法执行了两个相对独立的操作(从设计上来说,这时候应该拆分成两个独立的方法): 错误,不要使用"and"来连接参数 ``` - (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes; ``` 正确,使用"and"来表示两个相对独立的操作 ``` - (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag; ``` 9.命名规范--分组命名 * 使用英文,首字母大写,之后每个单词首字母都大写 * 每个分组使用模块的名字 * 使用的开源库统一放在“Library”分组下 * 使用的公共组件统一放在“Common”分组下 * 视图控制器及AppDelegate统一放在“Controllers”分组下 10.命名规范--图片命名 * 使用英文,首字母大写,之后每个单词首字母都大写 * 添加模块名作为前缀,避免冲突 * 图片应该与类文件一样,按模块分组放置 * 只要文件名叫做Icon.png,就会自动被当做是应用程序的图标 * 一个应用程序可以准备多种规格的图标,详情可以查看苹果官方文档,ios7 doc set/user expreience/guides/app icons on iPad and iphone * 一个app在启动过程中会全屏显示叫做Default.png的图片 11.命名规范--特殊类命名 * 如果是视图控制器的子类应添加后缀“ViewController”或者“Controller” * 如果是视图的子类应添加后缀“View” * 如果是按钮的子类应添加后缀“Button” 命名规范--补充命名 常量(预定义,局部常量等)使用小写k开头的驼峰法 举例:kInvalidHandle , kWritePerm 枚举类型命名首字母大写,之后每个单词首字母都大写,最后加“s” 枚举变量使用枚举类型去掉“s”作为前缀,每个单词首字母大写,中间不允许加下划线 举例: typedef enum UIControlEvents{ UIControlEventTouchDown, UIControlEventTouchUpInside }UIControlEvents; ## 编码规范 1.编码规范--判断nil或者YES/NO ``` if (someObject) { ... } if (!someObject) { ... } ``` 2.编码规范--条件赋值 如果是存在就赋值本身, 那就可以这样简写 ``` result = object ? : [self createObject]; ``` 3.编码规范--初始化方法 初始化的时候,直接赋值的好处是: 第一个好处还是简洁 第二个好处是可以防止初始化进去nil值造成crash ``` NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve"]; NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal"}; NSNumber *shouldUseLiterals = @YES; NSNumber *buildingZIPCode = @10018; ``` 4.编码规范--定义属性 1)建议定义属性的时候把所有的参数写全, 尤其是如果想定义成只读的(防止外面修改)那一定要加上readonly, 这也是代码安全性的一个习惯. 2)如果是内部使用的属性, 那么就定义成私有的属性(定义到.m的class extension里面) 对于拥有Mutable子类型的对象(e.g. NSString, NSArray, NSDictionary)一定要定义成copy属性. Why? 示例: NSArray的array = NSMutableArray的mArray; 如果mArray在某个地方改变了, 那array也会跟着改变. So, make sense? 3)尽量不要暴露mutable类型的对象在public interface, 建议在.h定义一个Inmutable类型的属性, 然后在.m的get函数里面返回一个内部定义的mutable变量. Why? For security as well! @property (nonatomic, readwrite, copy) NSString *name; 5.编码规范--BOOL赋值 ``` BOOL isAdult = age > 18; ``` 6.编码规范--拒绝死值 1)死值每次修改的时候容易被遗忘, 地方多了找起来就悲剧了. 而且定义成枚举或者static可以让错误发生在编译阶段. 另外仅仅看到一个数字, 完全不知道这个数字代表的意义. 纳尼? ``` if (car == Car.Nissan) or const int adultAge = 18; if (age > adultAge) { ... } ``` 7.编码规范--复杂的条件判断 清晰明了, 每个函数只做一件事! ``` if ([self canDeleteJob:job]) { ... } - (BOOL)canDeleteJob:(Job *)job { BOOL invalidJobState = job.JobState == JobState.New || job.JobState == JobState.Submitted || job.JobState == JobState.Expired; BOOL invalidJob = job.JobTitle && job.JobTitle.length; return invalidJobState || invalidJob; } ``` 8.编码规范--嵌套判断 一旦发现某个条件不符合, 立即返回, 条理更清晰 ``` if (!user.UserName) return NO; if (!user.Password) return NO; if (!user.Email) return NO; return YES; ``` 9.编码规范--参数过多 当发现实现某一功能需要传递的参数太多时, 就预示着你应该聚合成一个model类了...这样代码更整洁, 也不容易因为参数太多导致出错 ``` user里面有userName、password、email - (void)registerUser(User *user) { // to do... } ``` 10.编码规范--把方法进行分类 1)使用#pragma mark –来分类方法 #pragma mark – Life Cycle//代表生命周期方法 #pragma mark - Events//代表事件 #pragma mark – Private Methods//代表私有方法 #pragma mark - UITextFieldDelegate//代理 #pragma mark - UITableViewDataSource//数据源 #pragma mark - UITableViewDelegate//代理 #pragma mark - Custom Delegates//自定义代理 #pragma mark – Getters and Setters//getter和setter方法 11.编码规范--注释符号 ``` /*************************************************************************** * 文件引用 ***************************************************************************/ /*************************************************************************** * 宏定义 ***************************************************************************/ /*************************************************************************** * 常量 ***************************************************************************/ /*************************************************************************** * 类型定义 ***************************************************************************/ /*************************************************************************** * 全局变量 ***************************************************************************/ /*************************************************************************** * 原型 ***************************************************************************/ / *************************************************************************** * 类特性 ***************************************************************************/ / *************************************************************************** * 类的实现 ***************************************************************************/ ``` * 块代码注释符号 1)块注释风格1 ``` ///////////////////////////////////// // @name UIButton控件生成相关API ///////////////////////////////////// ``` 2)块注释风格2 ``` // // 块功能说明 // ``` 3)块注释风格3 ``` /* * 块功能说明 */ ``` * 使用#program mark - 注释内容 >#program mark 是每个ios程序员都必须会用的技巧,通过#program mark 把代码分为个个部分,良好的注释是好代码的开始 * 注释原则 >代码中尽量少注释,让代码能自我描述。不过当需要注释的时候,能需要清除的解释某个代码块的含义和作用。注释应当保持最新,如果不必要请删除。 * #pragma clang 使用 1)取消xcode编译器内对于启用方法的警告 ``` #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" // CODE:这段中出现使用所有的弃用方法都不会产生告警 #pragma clang diagnostic pop ``` 2)取消对未使用变量的警告,使用方法和上面相同,必须成对出现#pragma clang ``` //方法1: diagnostic --- #pragma clang diagnostic ignored "-Wunused-variable" //方法2: #pragma unused (foo) ``` 3)忽略内存泄露告警 ``` #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [someController performSelector: NSSelectorFromString(@"someMethod")] #pragma clang diagnostic pop ``` * 手动产生一条警告 ``` #warning :手动产生一条告警 ``` * 手动产生一条错误 ``` #error : 手动产生一条错误 ``` 12.编码规范--花括号空格 ``` - (void)methodName:(NSString *)string { ↑空格 ↑空格,推荐花括号在一行 if () { 空格↑ ↑空格,花括号不要另起一行 } else { 要换行↑ ↑空格,花括号不要另起一行 } } ``` 13.编码规范--编码注意 * 单个文件方法数不应超过30个 * 不要按类别排序(如把IBAction放在一块),应按任务把相关的组合在一起 * 禁止出现超过两层循环的代码,用函数或block替代 14.编码规范--编码例子 * 糟糕 ``` - (Task *)creatTaskWithPath:(NSString *)path { Task *aTask; if ([path isURL]) { if ([fileManager isWritableFileAtPath:path]) { if (![taskManager hasTaskWithPath:path]) { aTask = [[Task alloc] initWithPath:path]; } else { return nil; } } else { return nil; } } else { return nil; } return aTask; } ``` *建议 ``` - (Task *)creatTaskWithPath:(NSString *)path { if (![path isURL]) { return nil; } if (![fileManager isWritableFileAtPath:path]) { return nil; } if ([taskManager hasTaskWithPath:path]) { return nil; } Task *aTask = [[Task alloc] initWithPath:path]; return aTask; } ``` 15.编码规范--编码注释适当使用 * 尽量让代码可以自表述,而不是依赖注释。 * 注释应该表达那些代码没有表达以及无法表达的东西。 * 如果一段注释被用于解释一些本应该由这段代码自己表达的东西,我们就应该将这段注释看成一个改变代码结构或编码惯例直至代码可以自我表达的信号。 * 我们重命名那些糟糕的方法和类名,而不是去修补。我们选择将长函数中的一些代码段抽取出来形成一些小函数,这些小函数的名字可以表述原代码段的意图,而不是对这些代码段进行注释。 * 尽可能的通过代码进行表达。你通过代码所能表达的和你想要表达的所有事情之间的差额将为注释提供了一个合理的候选使用场合。对那些代码无法表达的东西进行注释,而不要仅简单地注释那些代码没有表达的东西。 * 方法内部禁止使用块注释。除非要临时注释大段代码,一般情况总应使用行注释。 16.编码规范--参数分行 * 正确使用:如果第一段名称过短,后续名称可以以Tab的长度(4个空格)为单位进行缩进 ``` - (void)short:(GTMFoo *)theFoo longKeyword:(NSRect)theRect evenLongerKeyword:(float)theInterval error:(NSError **)theError { ... } ``` * 错误使用:要么写在一行,要么全部分行 ``` [myObject doFooWith:arg1 name:arg2 error:arg3]; [myObject doFooWith:arg1 name:arg2 error:arg3]; ``` 17.编码规范--冒号两边 * 正确,在语法糖的"[]"或者"{}"两端留有空格 ``` NSArray *array = @[ [foo description], @"Another String", [bar description] ]; NSDictionary *dict = @{ NSForegroundColorAttributeName : [NSColor redColor] }; ``` * 构造字典时,字典的Key和Value与中间的冒号:都要留有一个空格 * 正确,冒号':'前后留有一个空格 ``` NSDictionary *option1 = @{ NSFontAttributeName : [NSFont fontWithName:@"Helvetica-Bold" size:12], NSForegroundColorAttributeName : fontColor }; ``` * 正确,按照Value来对齐 ``` NSDictionary *option2 = @{ NSFontAttributeName : [NSFont fontWithName:@"Arial" size:12], NSForegroundColorAttributeName : fontColor }; ``` * 错误,冒号前应该有一个空格 ``` NSDictionary *wrong = @{ AKey: @"b", BLongerKey: @"c", }; ``` * 错误,每一个元素要么单独成为一行,要么全部写在一行内 ``` NSDictionary *alsoWrong= @{ AKey : @"a", BLongerKey : @"b" }; ``` * 错误,在冒号前只能有一个空格,冒号后才可以考虑按照Value对齐 ``` NSDictionary *stillWrong = @{ AKey : @"b", BLongerKey : @"c", }; ``` 18.编码规范--程序布局 1)程序布局目的:程序布局的目的是显示出程序良好的逻辑结构,提高程序的准确性、连续性、可读性、可维护性。更重要的是,统一的程序布局和编程风格,有助于提高整个项目的开发质量,提高开发效率,降低开发成本。同时,对于普通程序员来说,养成良好的编程习惯有助于提高自己的编程水平,提高编程效率。因此,统一的、良好的程序布局和编程风格不仅仅是个人主观美学上的或是形式上的问题,而且会涉及到产品质量,涉及到个人编程能力的提高,必须引起大家重视。 2)布局中的空格:每个方法或者功能块之间为了结构清晰,应当有且只有一行空格,如下示例 ``` @interface SomeClass:NSObject @property (noatomic, strong) UIView *aView -(void)someMethod; @end @implementation SomeClass - (void)setAView:(NSInteger )aview { } -(void)someMethod { } @end ``` 3)布局中的Private Methods块:正常情况下ViewController里面不应该写 不是delegate方法的,不是event response方法的,不是life cycle方法的,就是private method了。对的,正常情况下ViewController里面一般是不会存在private methods的,这个private methods一般是用于日期换算、图片裁剪啥的这种小功能。这种小功能要么把它写成一个category,要么把他做成一个模块,哪怕这个模块只有一个函数也行。 ViewController基本上是大部分业务的载体,本身代码已经相当复杂,所以跟业务关联不大的东西能不放在ViewController里面就不要放。另外一点,这个private method的功能这时候只是你用得到,但是将来说不定别的地方也会用到,一开始就独立出来,有利于将来的代码复用。 4)布局中的初始化方法放哪里? 属性初始化放哪最好?建议在Getter中初始化 我看到很多APP,甚至我公司的项目,很多开发工程师,初始化属性的位置比较随意,有单独添加一个初始化方法类似setupView的,有在init初始化的,各种情况都有,我其实挺崩溃的,首先初始化方式不一致,其次这样做非常可能破坏了每个方法功能的单一性(每个方法只做一件事)。我比较习惯一个对象的"私有"属性写在extension里面,然后这些属性的初始化全部放在getter里面做,在init和dealloc之外,是不会出现任何类似_property这样的写法的。就是这样: ``` @interface CustomObject() @property (nonatomic, strong) UILabel *label; @end @implementation #pragma mark - getters and setters - (UILabel *)label { if (_label == nil) { _label = [[UILabel alloc] init]; _label.text = @"1234"; _label.font = [UIFont systemFontOfSize:12]; ... ... } return _label; } @end #pragma mark - life cycle - (void)viewDidLoad { [super viewDidLoad]; [self.view addSubview:self.label]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; self.label.frame = CGRectMake(1, 2, 3, 4); } ``` 5)布局中的Getters and Setters放在最底部 一个view可能会有非常多的view和其他属性,如果getters and setters放在前面,就会导致在implementation代码顶部有大量的初始化代码,这就导致主要的逻辑代码挪到后面去了,其他人阅读代码是不太方便的。 19.编码规范--表达式 * if语句 > 表达式大括号和其他大括号(if/else/switch/while 等.)总是在同一行语句打开但在新行中关闭。如果没有else 并且括号内只有一行语句,可以和if语句同行,并且不需要括号。 ``` if (user.isHappy) { //Do something } else { //Do something else } if (somethingIsBad) return something; ``` * Switch语句 > 大括号在case语句中并不是必须的,除非编译器强制要求。当一个case语句包含多行代码时,大括号应该加上。 ``` switch (condition) { case 1: // ... break; case 2: { // ... // Multi-line example using braces break; } case 3: // ... break; default: // ... break; } ``` >当在switch使用枚举类型时,'default'是不需要的 ``` RWTLeftMenuTopItemType menuType = RWTLeftMenuTopItemMain; switch (menuType) { case RWTLeftMenuTopItemMain: // ... break; case RWTLeftMenuTopItemShows: // ... break; case RWTLeftMenuTopItemSchedule: // ... break; } ``` #### 参考链接 http://www.jianshu.com/p/414bb5a53139