一 命名规则
比较常用的变量命名法则有三种:驼峰命名法;下划线命名法;帕斯卡命名法。
-
驼峰命名法:用的最广的命名法,变量名常用此命名,命名由一个单词或多个单词组合而成,首字母小写其余单词首字母大写,如:“userName”; 下划线命名法:每个单词间使用下划线“_”分割,所有字母均小写,如:“user_name”;
-
帕斯卡命名法:每个单词的首字母均大写的一串字符,与“骆驼命名法”的区别在于前者的首字母大写,后者的首字母小写。如:“UserName”。
1.项目命名规则
Xcode项目的命名个人推荐使用帕斯卡命名法则。IXueDai
2..类命名规则
帕斯卡命名法则。XHLoginViewController
我们在构建应用程序时,很有可能会有部分代码用于后续的项目,或者发布出去供他人使用,那么在别人使用你的类库或者你引用其他人的类库时很有可能出现相同命名的情况,从而引发出“重复符号错误”(duplicate symbol error),为了避免这种令人恼怒的情况发生,我们应该习惯于给自己的类添加一个前缀,可以是公司名称的缩写,也可以是你个人姓名的缩写,还有可能是框架名称的缩写,Xcode设置类前缀的地方在这里:
这里需要注意的是,推荐大家使用三个字母以上作为类的前缀,很多开发者都习惯于使用两个字母作为前缀(作者本人以前也是),但是苹果公司保留了两个字母作为类前缀的权利,大家可以发现苹果公司的类都是以框架名缩写并且都是两个字母作为前缀的,为了避免苹果公司将来新发布的框架缩写和你的前缀发生冲突,所以采用三个字母以上的前缀命名方案。不光类名如此,类目及延展或者自定义的结构体等同样推荐使用前缀。
项目中添加plist类型文件,不要命名为info.plist,以防止和系统自带的文件重名,发生莫名其妙的问题;
3.变量命名规则
- 普通变量(修饰+类型)
12
(nonatomic, strong) UILabel *titleLabel; //表示*标题*的label,是*UILabel*类型
(nonatomic, strong) UIButton *confirmButton; //表示*确认*的button,是*UIButton*类型 -
如果是声明BOOL类型,建议在括号中重写get方法
1
(nonatomic, readonly, getter = isKeyWindow) BOOL keyWindow;
NS_ENUM
和NS_OPTIONS
宏来定义枚举类型- 在常量前边加上字母k作为标记static const NSTimeInterval kAnimationDuration = 0.3
- 一些公开的常量通常使用类名作为前缀,同样是避免命名冲突而引发问题。案例:
-
UIKIT_EXTERN NSString *const UIApplicationDidEnterBackgroundNotification; // 常量命名
- 通知命名 使用const修饰,以Notification结尾
通知命名规则: [触发通知的类名] + [Did | Will] + [动作] + Notification错误示例:UIKIT_EXTERN NSString *const textFieldTextBeginEditingNotification;UIKIT_EXTERN NSString *const textFieldTextEndEditingNotification;正确操作:UIKIT_EXTERN NSString *const UITextFieldTextDidBeginEditingNotification;UIKIT_EXTERN NSString *const UITextFieldTextDidEndEditingNotification;
ps:这里面需要注意的是变量名尽量不要使用缩写,如我们经常可以看到很多开发者习惯于把根视图控制器写成rootVC或者mainVC等等,而系统给我们提供的却是完整的命名:self.window.rootViewController,假如系统给我们提供的是self.window.rootVC这种形式,以及其他命名方式也这样以非专业词汇的缩写命名,相信很多开发者会看的一头雾水。
4.宏命名规则
通常会把单词的所有字母大写,目的是为了告诉开发者这是一个宏,而不是一个普通的变量,当然如果宏的名称如果由多个单词组成,通常是每个单词之间使用下划线分割开,如:“JXL_ABC_DEF”这种形式。
5.方法命名规则
大部分方法可以分为两类:要什么 和 做什么
- 要什么表示取得某个对象,要以名词作为方法的开头如:“string”,“data”,“image”等,案例:
- (UIImage *)imageNamed:(NSString *)name;- (NSRange)rangeOfString:(NSString *)searchString; // 表明获取一个range
- 做什么表示执行某种操作,要以动词作为方法开头
-
- (void)setUpNavBar- (NSArray<ObjectType> *)sortedArrayUsingSelector:(SEL)comparator; // 表明目的是排序
- 如果该方法需要参数,每个参数前最好添加参数提示。如下面两种代码对比。
- (instancetype)init:(CGRect)frame; // 糟糕的方法命名 - (instancetype)initWithFrame:(CGRect)frame; // 好的方法命名
-
如果该方法需要多个参数,不能使用and这个单词连接参数
1
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;
- 一些代表过程监听的方法可能以“谁执行什么过程”这种形式命名,且动作发生之前通常使用“Will”,发生之后使用“Did”,询问是否发生使用“Should”。案例:
123
- (nullable NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath; //将要选择这一行cell- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath; //已经选择这一行cell- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(6_0); //是否高亮(选择这一行cell)
- 回调方法第一个参数是调用者
1
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions;- (void)buttonTapped:(UIButton*)sender;
- 类目方法:如果我们在编码中使用类目给一些系统的类拓展方法,那么推荐给这些方法添加前缀,目的很简单,就是避免和这个类的私有方法或者将来系统可能拓展的方法出现方法名相同的冲突。如我当初在写夜间模式的demo的时候给UIView写了一个类目,拓展的方法名我使用了姓名的缩写“jxl”作为方法的前缀:
-
- (void)jxl_setDayMode:(DAY_AND_NIGHT_MODE_BLOCK)dayMode nightMode:(DAY_AND_NIGHT_MODE_BLOCK)nightMode;
- 返回BOOL值得方法加前缀is,has
1
2- (BOOL)isEqualToString:(NSString *)aString;
- (BOOL)hasPrefix:(NSString *)aString;
二 代码编写规范
1.引用头文件
类的头文件尽量不要引用其他头文件,无需知道类的内部细节使用即可
2.使用类型常量替换#define预处理指令
在编写代码时,我们常用#define去定义一个宏。我们定义一个播放动画的时间为常量,可能这样定义:
#define ANIMATION_DURATION 1.0
上述预处理指令会把源代码中的 ANIMATION_DURATION 替换为1.0,可以达到效果,不过这样定义是存在问题的,定义出来的常量没有类型信息,无法一眼看出代表的是一个时间,可读性差,而且如果把这个宏定义放在头文件中的话,那么引入了这个头文件的代码,其 ANIMATION_DURATION 都会被替换,如果有人定义了常量值,这将导致应用程序中的常量值不一致。一种更好的处理方案是使用类型常量替换掉相应的#define预处理指令。
-
外部不可见:
.m文件中:
static const NSTimeInterval kAnimationDuration = 1.0;// 命名规则:不被外部访问时 k+变量名
static修饰:意味着仅在此编译单元(.m文件)中可见;const修饰:如果试图修改值,编译器就会报错;static const:二者都使用,编译器的处理效果和#define一样,把遇到的变量替换为常值。
- 外部可见: 有些时候是需要向外部公开某个常量的,For example,在使用通知中心的时候,你需要向其他对象派发通知,监听者需要知道监听的事件,这个事件的名称我们通常写成一个外界可见的常值变量,这样的话,监听者无需知道实际字符串的值,只需要以常值变量来作为自己监听的事件名称,系统的 UIApplicationDidEnterBackgroundNotification, UIApplicationWillEnterForegroundNotification等都是这样做的: 在.h文件中:
UIKIT_EXTERN NSString *const MyClassNameActionNameNotification; // 命名规则:类名+事件名+Notification
ps:从右至左解读,“一个常量,而这个常量是一个指针,指向NSString对象”。
在.m文件中:NSString *const MyClassNameActionNameNotification = @"MyClassNameActionNameNotification";
- 本例中的写法为: 在.h文件中:
UIKIT_EXTERN const NSTimeInterval MyClassNameAnimationDuration;// 命名规则:类名+变量名
在.m文件中:
const NSTimeInterval MyClassNameAnimationDuration = 0.3;
3.用枚举表示设置或状态
当我们想要表示某一种设置的多个选项或者多种状态时,推荐使用枚举。枚举的意义本来就是将一些表示某一种设置或者状态的数字转化成方便开发者阅读的形式,极大的提高了可读性。以上面其他的命名规则话题中提到过的枚举为例:
typedef NS_ENUM(NSInteger, UIViewAnimationTransition) { UIViewAnimationTransitionNone, // 枚举值命名 UIViewAnimationTransitionFlipFromLeft, UIViewAnimationTransitionFlipFromRight, UIViewAnimationTransitionCurlUp, UIViewAnimationTransitionCurlDown, };
这个枚举含有5个值,分别表示了5种动画状态,如果系统以“0”,“1”,“2”等这样的数字来表示状态的话,作为开发者想要知道每个数字代表什么样的动画效果只能一个一个的去测试,并且需要记住每一个数字代表什么状态,方便以后使用,那么我相信绝大多数开发者都会疯掉的。
4.协议的签订格式
协议的签订推荐下面这种写法,这种写法的好处是你签订了什么协议一目了然,而且后面填写注释看起来也会更加舒服。
FooViewController () < UITableViewDataSource, // 你的注释 UITableViewDelegate // 你的注释 > @end
5.代码整理
为了让你的代码更整洁,你需要将你的代码做好归类整理,例如一个ViewController实现文件里的代码可能是这样:
#pragma mark - Life Cycle
// Methods...#pragma mark - UITableViewDataSource
// Methods...
#pragma mark - UITableViewDelegate
// Methods...
#pragma mark - CustomDelegate
// Methods...
#pragma mark - Private Methods
// Methods…(.m中声明)
#pragma mark - Public Methods
// Methods…(.h中声明)
#pragma mark - Getters and Setters
// Methods...
#pragma mark - Notification
//Methods...
#pragma mark - Event Response
// Methods...
#pragma mark - Request Methods
//Methods...
6.实例变量声明时变量名前面加下划线“_”,局部变量不用加
错误示范:@implementation ViewController { UIButton *authCodeBtn; } - (void)viewDidLoad { [super viewDidLoad]; UIButton *_loginBtn = [[UIButton alloc] init]; } 好的习惯:@implementation ViewController { UIButton *_authCodeBtn; } - (void)viewDidLoad { [super viewDidLoad]; UIButton *loginBtn = [[UIButton alloc] init];
}
7.场景需求:在继承中,凡是要求子类重写父类的方法必须先调用父类的这个方法进行初始化操作;建议:父类的方法名后面加上NS_REQUIRES_SUPER; 子类重写这个方法就会自动警告提示要调用这个super方法,示例代码
// 注意:父类中的方法加`NS_REQUIRES_SUPER`,子类重写才有警告提示
- (void)prepare NS_REQUIRES_SUPER;
8.成员变量 setter&getter
一些刚刚入门的iOS开发者总是纠结在给属性赋值或者取值的时候,究竟是直接操作成员变量还是通过使用setter或者getter进行赋值取值操作,关于这个问题很多人的观点也不相同,从性能上来说,由于Objective-C的消息机制使用setter和getter效率要比直接操作成员变量的效率低,从内存方面来说,setter和getter都有内存的处理,所以使用起来更安全,一个折中的办法是除Lazy Loading对象外,赋值时使用setter,取值时直接操作成员变量,这样既保障了内存的安全,又提升了效率,而且通常我们的取值操作又多于赋值操作。
尽可能使用点语法访问属性,但是访问其他实例对象使用括号
12345 | view.backgroundColor = [UIColor redColor]; [UIApplication sharedApplication].delegate; //推荐 [view setBackgroundColor:[UIColor redColor]]; UIApplication.sharedApplication.delegate; //不推荐
|
9.定义属性尽可能写全参数
1 | @property (nonatomic, readwrite, copy) NSString *name; |
- 如果是内部使用的属性, 需定义成该类的私有属性(写在.m文件的class extension里)
- 对于拥有Mutable子类型的对象, 例如NSString NSArray NSDictionary NSDictionary, 一定要定义成copy属性
- 尽量不要暴露mutable类型的对象在public interface, 建议在.h定义一个Inmutable类型的属性, 然后在.m的get函数里面返回一个内部定义的mutable变量
- 不要出现混合声明,尽可能都使用@property声明
10.使用字面量语法替换等价方法
字面量语法实际上是一种“语法糖”(syntactic sugar),以一种非常简单快捷的方式能创建对象,使我们开发者编程更高效,更愉悦。目前Objective-C支持的字面量语法的类有NSString,NSNumber, NSArray, NSDictionary。使用字面量语法的好处:
-
使用字面量语法可以缩减源代码长度,没有多余语法成分,提高可读性;
-
在使用字面量语法创建数组时,如果数组元素对象中有nil,则会抛出异常,其效果等于先创建一个数组,然后把方括号内的所有对象都加到这个数组中。抛出的异常会是这样:
***Terminating app due to uncaught exception‘NSInvalidArgumentException', reason:’***-[__NSPlaceholderArray initWithObjects:count:] : attempt toinsert nil object from objects[0]'
案例:
NSArray *arrayA = [NSArray arrayWithObjects:object1, object2, object3, nil];
NSArray *arrayB = @[object1, object2, object3];
NSNumber *isHide = @NO;
NSNumber *errorCode = @404;针对上面代码进行分析,如果object2=nil;,arrayA数组可以创建,但只有object1一个对象,因为“+ (instancetype)arrayWithObjects:”方法会一次处理各个参数,直到发现nil为止,而arrayB会抛出异常,这个特性使我们更容易发现程序中存在的问题,提高了安全性。ps:字典跟数组一样,一旦有值为nil,也会抛出异常,而且创建时的“键”“值”顺序和我们正常说的“键值”顺序一样(正常初始化为“值”“键”),便于阅读。
使用字面量语法的缺点:使用字面量创建都是不可变对象,如果想创建可变对象需要复制一份:
NSMutableArray *mutableArray = [@[@1, @2, @3] mutableCopy];
11.判断nil或者YES/NO
12345678 | if (obj) { //... }if (!obj) { //... } |
12.BOOL类型赋值
错误示例: Bool isAdult;if (age > 18){ isAdult = YES; } else { isAdult = NO; }// 好的习惯 Bool isAdult; isAdult = age > 18;
1 | BOOL isAdult = age > 18; |
13. 复杂的条件判断
如果判断较为复杂,尽可能写到一个方法里
12345678910111213141516 | if ([self canDeleteAccount:account]) { //... }/** method */ - (BOOL)canDeleteAccount:(account) { if (account.balance == 0 || account.owner.isDead == YES || account.isCancel == YES) { return YES; } else { return NO; } } |
14.嵌套判断
错误示例:if(userName.length){ if (passWord.length) { //可以登录 } } 好的习惯:if(!userName.length){ return; };if(!passWord.length){ return; };
1 2 3 | if (!user.account) return NO; if (!user.password) return NO; return YES; |
15.加载xib
加载xib名称使用 NSStringFromClass()
1 | [self.tableView registerNib:[UINib nibWithNibName:NSStringFromClass([UserListCell class]) bundle:nil] forCellReuseIdentifier:ID]; |
16.判断if书写方式
1 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ if (indexPath.row == 0) return 40; if (indexPath.row == 1) return 50; if (indexPath.row == 2) return 60; return 44;} |
17.对于参数很多的函数,参数各占一行,并以冒号对齐,如:
1 | - (void)exampleFunctionWithPara:(int)para1 anotherPara:(int)para2 theThirdPara:(int)para3 theFourthPara:(int)para4{ ...} |
1 | [self exampleFunctionWithPara:1 anotherPara:2 theThirdPara:3 theFourthPara:4 theFifthPara:5]; |
18.运算符号间要留有合适的间隔
好的习惯: sum = value1 + value2;//瞬间 高大上UILable *lbl = [[UILable alloc] init] ;
19.字典构造时的注意点 :
前后要留有一个空格
错误示范: NSDictionary *attributs = @{ NSForegroundColorAttributeName:[UIColor orangeColor], NSFontAttributeName:[UIFont systemFontOfSize:12] }; 正确示例: NSDictionary *attributs = @{ NSForegroundColorAttributeName : [UIColor orangeColor], NSFontAttributeName : [UIFont systemFontOfSize:12] };
20.对一些相同的东西避免写死,特别是控件的frame 不便于修改
错误示例: UITextField *phoneTf = [[UITextField alloc] initWithFrame:CGRectMake(10, 0, 100, 40)]; [self.view addSubview:phoneTf]; UITextField *passwordTf = [[UITextField alloc] initWithFrame:CGRectMake(10, 110, 100, 40)]; [self.view addSubview:passwordTf];// 正确的方式: CGFloat margin = 10; CGFloat width = 100; CGFloat height = 40; UITextField *phoneTf = [[UITextField alloc] initWithFrame:CGRectMake(margin, 0, width, height)]; [self.view addSubview:phoneTf]; UITextField *passwordTf = [[UITextField alloc] initWithFrame:CGRectMake(margin, CGRectGetMaxY(phoneTf.frame) + margin, width, height)]; [self.view addSubview:passwordTf];
21.避免循环引用
如果【block内部】使用【外部声明的强引用】访问【对象A】, 那么【block内部】会自动产生一个【强引用】指向【对象A】
如果【block内部】使用【外部声明的弱引用】访问【对象A】, 那么【block内部】会自动产生一个【弱引用】指向【对象A】
__weak typeof(self) weakSelf = self; dispatch_block_t block = ^{ [weakSelf doSomething]; // weakSelf != nil// preemption, weakSelf turned nil [weakSelf doSomethingElse]; // weakSelf == nil }; 最好这样调用: __weak typeof(self) weakSelf = self; myObj.myBlock = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) { [strongSelf doSomething]; // strongSelf != nil// preemption, strongSelf still not nil(抢占的时候,strongSelf 还是非 nil 的) [strongSelf doSomethingElse]; // strongSelf != nil }else { // Probably nothing... return; } };
weakSelf是为了block不持有self,避免循环引用,而再声明一个strongSelf是因为一旦进入block执行,就不允许self在这个执行过程中释放。block执行完后这个strongSelf会自动释放,没有循环引用问题。
22.建议:
用CGSizeZero 代替 CGSizeMake(0,0);
CGRectZero代替CGRectMake(0, 0, 0, 0);
CGPointZero代替CGPointMake(0, 0)
23.在导航控制中,或它的子控制器,设置导航栏的标题应该用self.navigationItem.title = @“标题”而不建议self.title = @“标题”;