Objective-C编码规范

原文链接: GitHub
原文作者: raywenderlich.com Team
译文出自: raywenderlich.com Objective-C编码规范

这篇编码风格指南概括了raywenderlich.com的编码规范,可能有些删减或修改。

介绍

我们制定Objective-C编码规范的原因是我们能够在我们的书,教程和初学者工具包的代码保持优雅和一致。即使我们有很多不同的作者来完成不同的书籍。

这里编码规范有可能与你看到的其他Objective-C编码规范不同,因为它主要是为了打印和web的易读性。

关于作者

这编码规范的创建是由很多来自raywenderlich.com团队成员在Nicholas Waynik的带领下共同完成的。团队成员有:Soheil Moayedi Azarpour, Ricardo Rendon Cepeda, Tony Dahbura, Colin Eberhardt, Matt Galloway, Greg Heo, Matthijs Hollemans, Christopher LaPollo, Saul Mora, Andy Pereira, Mic Pringle, Pietro Rea, Cesare Rocchi, Marin Todorov, Nicholas WaynikRay Wenderlich

我们也非常感谢New York TimesRobots & Pencils'Objective-C编码规范的作者。这两个编码规范为本指南的创建提供很好的起点。

背景

这里有些关于编码风格Apple官方文档,如果有些东西没有提及,可以在以下文档来查找更多细节:

目录

语言

应该使用US英语.

应该:

UIColor *myColor = [UIColor whiteColor];

不应该:

UIColor *myColour = [UIColor whiteColor];

代码组织

在函数分组和protocol/delegate实现中使用#pragma mark -来分类方法,要遵循以下一般结构:

#pragma mark - Lifecycle
- (instancetype)init {}
- (void)dealloc {}
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
- (void)didReceiveMemoryWarning {}

#pragma mark - Custom Accessors
- (void)setCustomProperty:(id)value {}
- (id)customProperty {}

#pragma mark - IBActions/Event Response
- (IBAction)submitData:(id)sender {}
- (void)someButtonDidPressed:(UIButton*)button

#pragma mark - Protocol conformance
#pragma mark - UITextFieldDelegate
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate

#pragma mark - Public
- (void)publicMethod {}

#pragma mark - Private
- (void)privateMethod {}

#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {}

#pragma mark - NSObject
- (NSString *)description {}

空格

  • 缩进使用4个空格,确保在Xcode偏好设置来设置。(raywenderlich.com使用2个空格)
  • 方法大括号和其他大括号(if/else/switch/while 等.)总是在同一行语句打开但在新行中关闭。

应该:

if (user.isHappy) {
    //Do something
} else {
    //Do something else
}

不应该:

if (user.isHappy)
{
  //Do something
}
else {
  //Do something else
}
  • 在方法之间应该有且只有一行,这样有利于在视觉上更清晰和更易于组织。在方法内的空白应该分离功能,但通常都抽离出来成为一个新方法。
  • 优先使用auto-synthesis。但如果有必要,@synthesize@dynamic应该在实现中每个都声明新的一行。
  • 应该避免以冒号对齐的方式来调用方法。因为有时方法签名可能有3个以上的冒号和冒号对齐会使代码更加易读。请不要这样做,尽管冒号对齐的方法包含代码块,因为Xcode的对齐方式令它难以辨认。

应该:

// blocks are easily readable
[UIView animateWithDuration:1.0 animations:^{
  // something
} completion:^(BOOL finished) {
  // something
}];

不应该:

// colon-aligning makes the block indentation hard to read
[UIView animateWithDuration:1.0
                 animations:^{
                     // something
                 }
                 completion:^(BOOL finished) {
                     // something
                 }];

注释

当需要注释时,注释应该用来解释这段特殊代码为什么要这样做。任何被使用的注释都必须保持最新或被删除。

一般都避免使用块注释,因为代码尽可能做到自解释,只有当断断续续或几行代码时才需要注释。例外:这不应用在生成文档的注释

命名

Apple命名规则尽可能坚持,特别是与这些相关的memory management rules (NARC)。

长的,描述性的方法和变量命名是好的。

应该:

UIButton *settingsButton;

不应该:

UIButton *setBut;

三个字符前缀应该经常用在类和常量命名,但在Core Data的实体名中应被忽略。对于官方的raywenderlich.com书、初学者工具包或教程,前缀'RWT'应该被使用。

常量应该使用驼峰式命名规则,所有的单词首字母大写和加上与类名有关的前缀。

应该:

static NSTimeInterval const RWTTutorialViewControllerNavigationFadeAnimationDuration = 0.3;

不应该:

static NSTimeInterval const fadetime = 1.7;

属性也是使用驼峰式,但首单词的首字母小写。对属性使用auto-synthesis,而不是手动编写@ synthesize语句,除非你有一个好的理由。

应该:

@property (strong, nonatomic) NSString *descriptiveVariableName;

不应该:

id varnm;

下划线

当使用属性时,实例变量应该使用self.来访问和改变。这就意味着所有属性将会视觉效果不同,因为它们前面都有self.

但有一个特例:在初始化方法里,实例变量(例如,_variableName)应该直接被使用来避免getters/setters潜在的副作用。

局部变量不应该包含下划线。

方法

在方法签名中,应该在方法类型(-/+ 符号)之后有一个空格。在方法各个段之间应该也有一个空格(符合Apple的风格)。在参数之前应该包含一个具有描述性的关键字来描述参数。

"and"这个词的用法应该保留。它不应该用于多个参数来说明,就像initWithWidth:height以下这个例子:

应该:
objc
- (void)setExampleText:(NSString *)text image:(UIImage *)image;
- (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag;
- (id)viewWithTag:(NSInteger)tag;
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;

不应该:

-(void)setT:(NSString *)text i:(UIImage *)image;
- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag;
- (id)taggedView:(NSInteger)tag;
- (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;
- (instancetype)initWith:(int)width and:(int)height;  // Never do this.

变量

变量尽量以描述性的方式来命名。单个字符的变量命名应该尽量避免,除了在for()循环。

星号表示变量是指针。例如, NSString *text 既不是 NSString* text 也不是 NSString * text,除了一些特殊情况下常量。

私有变量 应该尽可能代替实例变量的使用。尽管使用实例变量是一种有效的方式,但更偏向于使用属性来保持代码一致性。

通过使用'back'属性(_variable,变量名前面有下划线)直接访问实例变量应该尽量避免,除了在初始化方法(init, initWithCoder:, 等…),dealloc 方法和自定义的setters和getters。想了解关于如何在初始化方法和dealloc直接使用Accessor方法的更多信息,查看这里

应该:

@interface RWTTutorial : NSObject

@property (strong, nonatomic) NSString *tutorialName;

@end

不应该:

@interface RWTTutorial : NSObject {
  NSString *tutorialName;
}

属性特性

所有属性特性应该显式地列出来,有助于新手阅读代码。属性特性的顺序应该是storage、atomicity,与在Interface Builder连接UI元素时自动生成代码一致。

应该:

@property (weak, nonatomic) IBOutlet UIView *containerView;
@property (strong, nonatomic) NSString *tutorialName;

不应该:

@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic) NSString *tutorialName;

NSString应该使用copy 而不是 strong的属性特性。

为什么?即使你声明一个NSString的属性,有人可能传入一个NSMutableString的实例,然后在你没有注意的情况下修改它。

应该:

@property (copy, nonatomic) NSString *tutorialName;

不应该:

@property (strong, nonatomic) NSString *tutorialName;

点符号语法

点语法是一种很方便封装访问方法调用的方式。当你使用点语法时,通过使用getter或setter方法,属性仍然被访问或修改。想了解更多,阅读这里

点语法应该总是被用来访问和修改属性,因为它使代码更加简洁。[]符号更偏向于用在其他例子。

应该:
objc
NSInteger arrayCount = [self.array count];
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;

不应该:
objc
NSInteger arrayCount = self.array.count;
[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;

字面值

NSString, NSDictionary, NSArray, 和 NSNumber的字面值应该在创建这些类的不可变实例时被使用。请特别注意nil值不能传入NSArrayNSDictionary字面值,因为这样会导致crash。

应该:

NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone": @"Kate", @"iPad": @"Kamal", @"Mobile Web": @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingStreetNumber = @10018;

不应该:

NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil];
NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill", @"Mobile Web", nil];
NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];
NSNumber *buildingStreetNumber = [NSNumber numberWithInteger:10018];

常量

常量是容易重复被使用和无需通过查找和代替就能快速修改值。常量应该使用static来声明而不是使用#define,除非显式地使用宏。

应该:

static NSString * const RWTAboutViewControllerCompanyName = @"RayWenderlich.com";

static CGFloat const RWTImageThumbnailHeight = 50.0;

不应该:

#define CompanyName @"RayWenderlich.com"

#define thumbnailHeight 2

枚举类型

当使用enum时,推荐使用新的固定基本类型规格,因为它有更强的类型检查和代码补全。现在SDK有一个宏NS_ENUM()来帮助和鼓励你使用固定的基本类型。

例如:

typedef NS_ENUM(NSInteger, RWTLeftMenuTopItemType) {
  RWTLeftMenuTopItemMain,
  RWTLeftMenuTopItemShows,
  RWTLeftMenuTopItemSchedule
};

你也可以显式地赋值(展示旧的k-style常量定义):

typedef NS_ENUM(NSInteger, RWTGlobalConstants) {
  RWTPinSizeMin = 1,
  RWTPinSizeMax = 5,
  RWTPinCountMin = 100,
  RWTPinCountMax = 500,
};

旧的k-style常量定义应该避免除非编写Core Foundation C的代码。

不应该:

enum GlobalConstants {
  kMaxPinSize = 5,
  kMaxPinCount = 500,
};

Case语句

大括号在case语句中并不是必须的,除非编译器强制要求。当一个case语句包含多行代码时,大括号应该加上。

switch (condition) {
  case 1:
    // ...
    break;
  case 2: {
    // ...
    // Multi-line example using braces
    break;
  }
  case 3:
    // ...
    break;
  default: 
    // ...
    break;
}

有很多次,当相同代码被多个cases使用时,一个fall-through应该被使用。一个fall-through就是在case最后移除'break'语句,这样就能够允许执行流程跳转到下一个case值。为了代码更加清晰,一个fall-through需要注释一下。

switch (condition) {
  case 1:
    // ** fall-through! **
  case 2:
    // code executed for values 1 and 2
    break;
  default: 
    // ...
    break;
}

当在switch使用枚举类型时,'default'是不需要的。例如:

RWTLeftMenuTopItemType menuType = RWTLeftMenuTopItemMain;

switch (menuType) {
  case RWTLeftMenuTopItemMain:
    // ...
    break;
  case RWTLeftMenuTopItemShows:
    // ...
    break;
  case RWTLeftMenuTopItemSchedule:
    // ...
    break;
}

私有属性

私有属性应该在类的实现文件中的类扩展(匿名分类)中声明,命名分类(比如RWTPrivateprivate)应该从不使用除非是扩展其他类。匿名分类应该通过使用+Private.h文件的命名规则暴露给测试。

例如:

@interface RWTDetailViewController ()

@property (strong, nonatomic) GADBannerView *googleAdView;
@property (strong, nonatomic) ADBannerView *iAdView;
@property (strong, nonatomic) UIWebView *adXWebView;

@end

布尔值

Objective-C使用YESNO。因为truefalse应该只在CoreFoundation,C或C++代码使用。既然nil解析成NO,所以没有必要在条件语句比较。不要拿某样东西直接与YES比较,因为YES被定义为1和一个BOOL能被设置为8位。

这是为了在不同文件保持一致性和在视觉上更加简洁而考虑。

应该:

if (someObject) {}
if (![anotherObject boolValue]) {}

不应该:

if (someObject == nil) {}
if ([anotherObject boolValue] == NO) {}
if (isAwesome == YES) {} // Never do this.
if (isAwesome == true) {} // Never do this.

如果BOOL属性的名字是一个形容词,属性就能忽略"is"前缀,但要指定get访问器的惯用名称。例如:

@property (assign, getter=isEditable) BOOL editable;

文字和例子从这里引用Cocoa Naming Guidelines

条件语句

条件语句主体为了防止出错应该使用大括号包围,即使条件语句主体能够不用大括号编写(如,只用一行代码)。这些错误包括添加第二行代码和期望它成为if语句;还有,even more dangerous defect可能发生在if语句里面一行代码被注释了,然后下一行代码不知不觉地成为if语句的一部分。除此之外,这种风格与其他条件语句的风格保持一致,所以更加容易阅读。

应该:

if (!error) {
  return success;
}

不应该:

if (!error)
  return success;

if (!error) return success;

三元操作符

当需要提高代码的清晰性和简洁性时,三元操作符?:才会使用。单个条件求值常常需要它。多个条件求值时,如果使用if语句或重构成实例变量时,代码会更加易读。一般来说,最好使用三元操作符是在根据条件来赋值的情况下。

Non-boolean的变量与某东西比较,加上括号()会提高可读性。如果被比较的变量是boolean类型,那么就不需要括号。

应该:

NSInteger value = 5;
result = (value != 0) ? x : y;

BOOL isHorizontal = YES;
result = isHorizontal ? x : y;

不应该:

result = a > b ? x = c > d ? c : d : y;

Init方法

Init方法应该遵循Apple生成代码模板的命名规则。返回类型应该使用instancetype而不是id

- (instancetype)init {
  self = [super init];
  if (self) {
    // ...
  }
  return self;
}

查看关于instancetype的文章Class Constructor Methods

类构造方法

当类构造方法被使用时,它应该返回类型是instancetype而不是id。这样确保编译器正确地推断结果类型。

@interface Airplane
+ (instancetype)airplaneWithType:(RWTAirplaneType)type;
@end

关于更多instancetype信息,请查看NSHipster.com

CGRect函数

当访问CGRect里的x, y, width, 或 height时,应该使用CGGeometry函数而不是直接通过结构体来访问。引用Apple的CGGeometry:

在这个参考文档中所有的函数,接受CGRect结构体作为输入,在计算它们结果时隐式地标准化这些rectangles。因此,你的应用程序应该避免直接访问和修改保存在CGRect数据结构中的数据。相反,使用这些函数来操纵rectangles和获取它们的特性。

应该:

CGRect frame = self.view.frame;

CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);
CGRect frame = CGRectMake(0.0, 0.0, width, height);

不应该:

CGRect frame = self.view.frame;

CGFloat x = frame.origin.x;
CGFloat y = frame.origin.y;
CGFloat width = frame.size.width;
CGFloat height = frame.size.height;
CGRect frame = (CGRect){ .origin = CGPointZero, .size = frame.size };

黄金路径

当使用条件语句编码时,左手边的代码应该是"golden" 或 "happy"路径。也就是不要嵌套if语句,多个返回语句也是OK。

应该:

- (void)someMethod {
  if (![someOther boolValue]) {
    return;
  }

  //Do something important
}

不应该:

- (void)someMethod {
  if ([someOther boolValue]) {
    //Do something important
  }
}

错误处理

当方法通过引用来返回一个错误参数,判断返回值而不是错误变量。

应该:

NSError *error;
if (![self trySomethingWithError:&error]) {
  // Handle Error
}

不应该:

NSError *error;
[self trySomethingWithError:&error];
if (error) {
  // Handle Error
}

在成功的情况下,有些Apple的APIs记录垃圾值(garbage values)到错误参数(如果non-NULL),那么判断错误值会导致false负值和crash。

单例模式

单例对象应该使用线程安全模式来创建共享实例。

+ (instancetype)sharedInstance {
  static id sharedInstance = nil;

  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sharedInstance = [[self alloc] init];
  });

  return sharedInstance;
}

这会防止possible and sometimes prolific crashes.

换行符

换行符是一个很重要的主题,因为它的风格指南主要为了打印和网上的可读性。

例如:

self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];

一行很长的代码应该分成两行代码,下一行用两个空格隔开。

self.productsRequest = [[SKProductsRequest alloc] 
  initWithProductIdentifiers:productIdentifiers];

Xcode工程

物理文件应该与Xcode工程文件保持同步来避免文件扩张。任何Xcode分组的创建应该在文件系统的文件体现。代码不仅是根据类型来分组,而且还可以根据功能来分组,这样代码更加清晰。

尽可能在target的Build Settings打开"Treat Warnings as Errors,和启用以下additional warnings。如果你需要忽略特殊的警告,使用 Clang's pragma feature

其他Objective-C编码规范

如果我们的编码规范不符合你的口味,可以查看其他的编码规范:

2018/7/3 posted in  Program

iOS开发中的代码布局规范

1.指导原则

【原则1-1】首先是为人编写程序,其次才是计算机。
说明:这是软件开发的基本要点,软件的生命周期贯穿产品的开发、测试、生产、用户使用、版本升级和后期维护等长期过程,只有易读、易维护的软件代码才具有生命力。
【原则1-2】保持代码的简明清晰,避免过分的编程技巧。
说明:简单是最美。保持代码的简单化是软件工程化的基本要求。不要过分追求技巧,否则会降低程序的可读性。
【原则1-3】编程时首先达到正确性,其次考虑效率。
说明:编程首先考虑的是满足正确性、健壮性、可维护性、可移植性等质量因素,最后才考虑程序的效率和资源占用。
【原则1-4】编写代码时要考虑到代码的可测试性。
说明:不可以测试的代码是无法保障质量的,开发人员要牢记这一点来设计、编码。实现设计功能的同时,要提供可以测试、验证的方法。
【原则1-5】函数(方法)是为一特定功能而编写,不是万能工具箱。
说明:方法是一个处理单元,是有特定功能的,所以应该很好地规划方法,不能是所有东西都放在一个方法里实现

2.布局

程序布局的目的是显示出程序良好的逻辑结构,提高程序的准确性、连续性、可读性、可维护性。更重要的是,统一的程序布局和编程风格,有助于提高整个项目的开发质量,提高开发效率,降低开发成本。同时,对于普通程序员来说,养成良好的编程习惯有助于提高自己的编程水平,提高编程效率。因此,统一的、良好的程序布局和编程风格不仅仅是个人主观美学上的或是形式上的问题,而且会涉及到产品质量,涉及到个人编程能力的提高,必须引起大家重视。

2.1.文件布局

【规则2-1-1】遵循统一的布局顺序来书写头文件。
说明:以下内容如果某些节不需要,可以忽略。但是其它节要保持该次序。
头文件布局:

文件头
#import (依次为标准库头文件、非标准库头文件)
全局宏
常量定义
全局数据类型
类定义

正例:

/***************************************************************************
 *                                文件引用
 ***************************************************************************/ 
/***************************************************************************
 *                                 类引用
 ***************************************************************************/

/***************************************************************************
 *                                 宏定义
 ***************************************************************************/
/***************************************************************************
 *                                 常量
 ***************************************************************************/ 
/***************************************************************************
 *                                类型定义
 ***************************************************************************/ 
/ ***************************************************************************
 *                                 类定义
 ***************************************************************************/

【规则2-1-2】遵循统一的布局顺序来书写实现文件
说明:以下内容如果某些节不需要,可以忽略。但是其它节要保持该次序。
实现文件布局:

文件头(参见“注释”一节)
#import (依次为标准库头文件、非标准库头文件)
文件内部使用的宏
常量定义
文件内部使用的数据类型
全局变量
本地变量(即静态全局变量)
类的实现

正例:

/***************************************************************************
 *                                文件引用
 ***************************************************************************/ 
/***************************************************************************
 *                                 宏定义
 ***************************************************************************/
/***************************************************************************
 *                                 常量
 ***************************************************************************/ 
/***************************************************************************
 *                                类型定义
 ***************************************************************************/
/***************************************************************************
 *                                全局变量
 ***************************************************************************/
/***************************************************************************
 *                                 原型
 ***************************************************************************/
/ ***************************************************************************
 *                                类特性
 ***************************************************************************/
/ ***************************************************************************
 *                                类的实现
 ***************************************************************************/

2.2类结构布局

使用#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

2.2.1布局中的空格

每个方法或者功能块之间为了结构清晰,应当有且只有一行空格。

@interface SomeClass:NSObject

@property (noatomic, strong) UIView *aView

-(void)someMethod;

@end

@implementation SomeClass

- (void)setAView:(NSInteger )aview {

}

-(void)someMethod {

}

@end

2.2.2关于布局中的Private Methods块

2.2.3属性初始化放哪最好?建议在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);
}

唐巧说他喜欢的做法是用_property这种,然后关于_property的初始化通过[self setupProperty]这种做法去做。从刚才上面的代码来看,就是要在viewDidLoad里面多调用一个setup方法而已,然后我推荐的方法就是不用多调一个setup方法,直接走getter。

嗯,怎么说呢,其实两种做法都能完成需求。但是从另一个角度看,_苹果之所以选择让[self getProperty]和self.property可以互相通用,这种做法已经很明显地表达了苹果的倾向:希望每个property都是通过getter方法来获得_。

2.2.4Getters and Setters放在最底部

控制器可能会有非常多的view属性和其他属性,而这些代码没有太复杂的逻辑,往往就是一些固定的配置,放置在最后面是为了突出我们以上的逻辑代码,当然了最后面查看也很方便的。

3.表达式

3.1 IF语句

表达式大括号和其他大括号(if/else/switch/while 等.)总是在同一行语句打开但在新行中关闭。如果没有else 并且括号内只有一行语句,可以和if语句同行,并且不需要括号。

if (user.isHappy) { 
    //Do something
} else { 
    //Do something else
}

if (somethingIsBad) return something;

3.2SWITCH语句

大括号在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;
    }
2017/1/22 posted in  Program

iOS开发中的命名规范

关于命名

统一要求

含义清楚,尽量做到不需要注释也能了解其作用,若做不到,就加注释
使用全称,不适用缩写

  1. 一般性原则 可读性高(简洁且清晰)和防止命名冲突(通过加前缀后缀来保证)。Objective-C 的命名通常都比较长, 名称遵循驼峰式命名法. 一个好的命名标准很简单, 就是做到在开发者一看到名字时, 就能够懂得它的含义和使用方法. 另外, 每个模块都要加上自己的前缀, 前缀在编程接口中非常重要, 可以区分软件的功能范畴并防止不同文件或者类之间命名发生冲突, 比如相册模块(PhotoGallery)的代码都以PG作为前缀: PGAlbumViewController, PGDataManager.
代码 点评
insertObject:atIndex: Good
insert:at: 不清晰;要插⼊什么?“at”表⽰示什么?
removeObjectAtIndex: Good
removeObject: 不错,因为⽅法是⽤用来移除作为参数的对象
remove: 不清晰;要移除什么?
  1. 一致性 尽可能与Cocoa.编程接⼝命名保持一致。如果你不太确定某个命名的⼀致性,请浏览头文件或参考文档中的范例,在使⽤多态方法的类中,命名的⼀致性⾮常重要。在不同类中实现相同功能的⽅法应该具有同的名称。
代码 点评
– (NSInteger)tag 在 NSView, NSCell, NSControl 中有定义
– (void)setStringValue:(NSString *) 在许多 Cocoa classes 中都有定义

资源文件命名 (图片,本地化文件)

这个图片资源命名方式,以功能为组织形式,是一个很好的习惯,有利于查看资源文件。
原则:
1)采用单词全拼,或者大家公认无岐义的缩写(比如:nav,bg,btn等)
2)采用“模块+功能”命名法,模块分为公共模块、私有模块。公共模块主要包括统一的背景,导航条,标签,公共的按钮背景,公共的默认图等等;私有模块主要根据app的业务功能模块划分,比如用户中心,消息中心等。
例如用户中心用户头像图片的命名可以为:uc_imageview_user_icon

这部分规范可能是很有经验的设计提供,也有可能是我们开发人员提供,掌握总是没有坏处的。

我们的命名规则的基本思想是把文件名分成三部分,第一部分是图片的逻辑归属分类,第二部分是图片的表现内容,第三部分是图片的内容的类型,有些图片还会有第四部分,表示图片表现的状态。首先有几个规则是:

  • 用英文命名,不用拼音
  • 每一部分用下划线分隔
  • 图片名中两倍图在名字最后要加@2x,三倍图在名字最后要加@3x

类的命名

类名(以及类别、协议名)应首字母大写,并以驼峰格式分割单词
大驼峰式命名:每个单词的首字母都采用大写字母
例子:MFHomePageViewController

  1. 类的前缀 1)所有类名、枚举、结构、protocol定义时最好加一个统一的标示符,可以是项目缩写,或者个人项目的名称缩写,例如都加上全大写的Hoo(我的姓氏)作为前缀 2)根据功能模块可以在给功能模块的类添加功能模块的名称前缀,如用户中心的profileViewController.可以命名为HooUCProfileViewController.
  2. 类的后缀 所有protocol定义时,都加上后缀Delegate 。如,HooRefreshViewDelegate,表示RefreshView的协议; 所有的控制器都加上Controller,所有的通知名都加上Notification。

ViewController: 使用ViewController做后缀
例子: MFHomeViewController

View: 使用View做后缀
例子: MFAlertView

UITableCell:使用Cell做后缀
例子: MFNewsCell

Protocol: 使用Delegate或者DataSource作为后缀
例子: UITableViewDelegate

UI控件依次类推

类别命名

类名+标识+扩展(UIImageView +HP+Web)
例:如果我们想要创建一个基于UIImageView 的类别用于网络请求图片,我们应该把类别放到名字是UIImageView+HPWeb.h的文件里。UIImageView为要扩展的类名,HP为专属标识,Web为扩展的功能。

方法命名

方法名应遵守小驼峰原则,首字母小写,其他单词首字母大写,每个空格分割的名称以动词开头。执行性的方法应该以动词开头,小写字母开头,返回性的方法应该以返回的内容开头,但之前不要加get。如:

- (void)insertModel:(id)model atIndex:(NSUInteger)atIndex;
- (instancetype)arrayWithArray:(NSArray *)array;

1.代理方法
以发送代理的对象类名作为代理方法名的开始(去掉类名的前缀,并且小写开头)

- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;

私有变量

小驼峰式命名:第一个单词以小写字母开始,后面的单词的首字母全部大写
例子:firstName、lastName

以 _ 开头,第一个单词首字母小写
例子:NSString * _somePrivateVariable

私有变量放在 .m 文件中声明 

变量名使用小驼峰法, 使变量名尽量可以推测其用途属性具有描述性。别一心想着少打几个字母,让你的代码可以迅速被理解更加重要。每个属性命名都加上类型后缀,如,按钮就加上Button
后缀,模型就加上Model后缀。

@property (nonatomic, strong) UIButton *submitButton;

1)类成员变量名
  成员变量用小驼峰法命名并前缀下划线,如:

UIButton *_submitButton;

2)局部变量名
  遵守小驼峰命名规则,如:

NSInteger numCompletedConnections =3; 

property变量

小驼峰式命名
例子:///注释
@property (nonatomic, copy) NSString *userName;

禁止使用synthesize关键词

宏命名

全部大写,单词间用 _ 分隔。[不带参数]
例子: #define THIS_IS_AN_MACRO @"THIS_IS_AN_MACRO"

以字母 k 开头,后面遵循大驼峰命名。[不带参数]
例子:#define kWidth self.frame.size.width

小驼峰命名。[带参数]

#define getImageUrl(url) [NSURL URLWithString:[NSString stringWithFormat:@"%@%@",kBaseUrl,url]]

const常量

以小写k
开头,后面单词首字母大写,其余小写。如:

const float kMaxHeigt = 100.0f;

如果是特殊含义的常量也建议加上后缀,如通知加上Notification为后缀,如:

extern Nsstring * Const kLoginSuccessNotification

Enum枚举的命名

Enum类型的命名与类的命名规则一致,以Objective-C的方式命名枚举
Enum中枚举内容的命名需要以该Enum类型名称开头
例子:

typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
    UIViewAnimationTransitionNone,
    UIViewAnimationTransitionFlipFromLeft,
    UIViewAnimationTransitionFlipFromRight,
    UIViewAnimationTransitionCurlUp,
    UIViewAnimationTransitionCurlDown,
};

Delegate命名

类的实例必须为回调方法的参数之一, 如

- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section

回调方法的参数只有类自己的情况,方法名要符合实际含义, 如:

- (NSInteger)numberOfSectionsInTableView:(UITableView*)tableView

以类的名字开头(回调方法存在两个以上参数的情况)以表明此方法是属于哪个类的, 如:

- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

使用did和will通知Delegate已经发生的变化或将要发生的变化, 如:

- (NSIndexPath*)tableView:(UITableView*)tableView willSelectRowAtIndexPath:(NSIndexPath*)indexPath;
- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath;

私有方法及变量声明

声明位置

在.m文件中最上方,定义空的category进行声明
例子:

#import "CodeStandardViewController.h"
    // 在这个category(类目)中定义变量和方法
    @interface CodeStandardViewController (){
      // 声明私有变量
    }

     // 私有方法
    - (void)samplePrivateMethod;
    @end

    @implementation CodeStandardViewController
    // 私有方法的实现
    - (void)samplePrivateMethod{
      //some code
    }

关于注释

代码中尽量少注释,让代码能自我描述。不过当需要注释的时候,能需要清除的解释某个代码块的含义和作用。注释应当保持最新,如果不必要请删除。

  • 最好的代码是不需要注释的 尽量通过合理的命名
  • 良好的代码把含义表达清楚 在必要的地方添加注释
  • 注释需要与代码同步更新
  • 如果做不到命名尽量的见名知意的话,就可以适当的添加一些注释或者mark ###属性注释 例子: /// 学生 @property (nonatomic, strong) Student *student;

方法声明注释:

/** 
   * @brief 登录验证
   *
   * @param personId 用户名
   * @param password 密码
   * @param complete 执行完毕的block
   *
   * @return
   */
  + (void)loginWithPersonId:(NSString *)personId password:(NSString *)password complete:(void (^)(CheckLogon *result))complete;

关于UI布局

 使用Interface Builder进行界面布局
 Xib文件的命名与其对应的.h文件保持相同
 Xib文件中控件的组织结构要合理,Xib文件中控件需要有合理的可读性强的命名,方便他人理解

格式化代码

指针 "*" 位置

 
定义一个对象时,指针 "*" 靠近变量
例子: NSString *userName;

方法的声明和定义

在 - 、+ 和 返回值 之间留一个空格,方法名和第一个参数之间不留空格

- (id)initWithNibName:(NSString *)nibNameOrNilbundle:(NSBundle *)nibBundleOrNil
{...}

代码缩进

使用 xcode 默认缩进,即 tab = 4空格
使用 xcode 中 re-indent 功能定期对代码格式进行整理
相同类型变量声明需要独行声明
例子:

CGFloatoringX = frame.origin.x;
CGFloatoringY = frame.origin.y;
CGFloatlineWidth = frame.size.width;

Method与Method之间空一行
例子:

#pragma mark - private methods
- (void)samplePrivateMethod
{...}

- (void)sampleForIf
{...}

对method进行分组

使用 #pragma mark - 方式对类的方法进行分组
例子:

#pragma mark - private methods

  - (void)samplePrivateMethod
  {...}

  - (void)sampleForIf
  {...}

  - (void)sampleForWhile
  {...}
  
  - (void)sampleForSwitch
  {...}

  - (void)wrongExamples
  {...}

  #pragma mark - public methods
  - (void)samplePublicMethodWithParam:(NSString*)sampleParam
  {...}

  #pragma mark - life cycle methods
  - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
  {...}

  - (void)viewDidLoad
  {...}

  - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
  {...}

大括号写法

对于类的method: 左括号跟在第一行后边(遵循苹果官方文档)
例子:

- (id)initWithNibName:(NSString *)nibNameOrNilbundle:(NSBundle *)nibBundleOrNil{
      self = [super initWithNibName:nibNameOrNil

      bundle:nibBundleOrNil];

      if (self) {
            // Custom initialization
        }

      return self;
}

- (void)sampleForIf{
    BOOL someCondition = YES;
    if(someCondition) {
        // do something here
    }
}
- (void)sampleForWhile{
    int i = 0;
    while (i < 10) {
        // do something here
        i = i + 1;
    }
}
- (void)sampleForSwitch{
    SampleEnum testEnum = SampleEnumTwo;
    switch(testEnum) {
        caseSampleEnumUndefined:{
            // do something
            break;
        }
        caseSampleEnumOne:{
            // do something
            break;
        }
        caseSampleEnumTwo:{
            // do something
            break;
        }
        default:{
            NSLog(@"WARNING: there is an enum type not handled properly!");
            break;
        }
    }

任何需要写大括号的部分,不得省略
错误示例:

- (void)wrongExamples{
    BOOLsomeCondition = YES;
    if (someCondition)
        NSLog(@"this is wrong!!!");
    while(someCondition)
        NSLog(@"this is wrong!!!");
}
2017/1/19 posted in  Program

iOS项目的目录结构和开发流程

原文出处: Limboy

网上相关的资源不多,开源的且质量还不错的iOS项目也是少之又少,最近正好跟同事合作了一个iOS项目,来说说自己的一些想法。

目录结构


AppDelegate

Models

Macro

General

Helpers

Vendors

Sections

Resources

一个合理的目录结构首先应该是清晰的,让人一眼看上去就能大概了解目录的职责,且容易应对新的变化。

AppDelegate

这个目录下放的是AppDelegate.h(.m)文件,是整个应用的入口文件,所以单独拿出来。

Models

这个目录下放一些与数据相关的Model文件,里面大概是这样:

Models

    |-
 BaseModel.h

    |-
 BaseModel.m

    |-
 CollectionModel.h

    |-
 CollectionModel.m

    ...

Macro

这个目录下放了整个应用会用到的宏定义,里面大概是这样:

Macro

    |-
 AppMacro.h

    |-
 NotificationMacro.h

    |-
 VendorMacro.h

    |-
 UtilsMacro.h

    ...

AppMacro.h 里放app相关的宏定义,如:

//
 表情相关

#define
 EMOTION_CACHE_PATH @"cachedemotions"

#define
 EMOTION_RECENT_USED @"recentusedemotions"

#define
 EMOTION_CATEGORIES @"categoryemotions"

#define
 EMOTION_TOPICS @"emotiontopics"

 

//
 收藏相关

#define
 COLLECT_CACHE_PATH @"collected"

 

//
 配图相关

#define
 WATERFALL_ITEM_HEIGHT_MAX 300

#define
 WATERFALL_ITEM_WIDTH 146

NotificationMacro.h 里放的是通知相关的宏定义。
UtilsMacro.h 里放的是一些方便使用的宏定义,如:

define
 UIColorFromRGB(r,g,b) [UIColor \

colorWithRed:r/255.0
 \
green:g/255.0
 \
blue:b/255.0
 alpha:1]
#define
 NSStringFromInt(intValue) [NSString stringWithFormat:@"%d",intValue]

VendorMacro.h 里放一些第三方常量,如:

#define
 UMENG_KEY @"xxxxx"

#define
 UMENG_CHANNEL_ID @"xxx"

如果有新的类型的宏定义,可以再新建一个相关的Macro.h。

General

这个目录放会被重用的Views/Classes和Categories。里面大概是这样:

General

    |-
 Views

        |-
 TPKScollView

        |-
 TPKPullToRefresh

        ...

    |-
 Classes

        |-
 TPKBaseViewController

        |-
 TPKHorizontalView

        ...

    |
 - Categories

        |-
 UIViewController+Sizzle

        |-
 UIImageView+Downloader

        ...

这里的TPK是项目的首字母缩写。

Helpers

这个目录放一些助手类,文件名与功能挂钩。里面大概是这样:


Helpers

    |-
 TPKShareHelper

    |-
 TPDBHelper

    |-
 TPKEmotionHelper

    ...

助手类的主要作用是帮助Controller瘦身,也可以提供一定程度的复用。

Vendors

这个目录放第三方的类库/SDK,如UMeng、WeiboSDK、WeixinSDK等等。

Sections

这个目录下面的文件对应的是app的具体单元,如导航、瀑布流等等。里面大概是这样:


Sections

    |-
 Menu

    |-
 Setting

    |-
 Collection

    ...

Resources

这个目录下放的是app会用到的一些资源,主要是图片。

Cocoapods

业务无关的类库可以通过 Cocoapods 来方便地管理,如SDWebImage, Reachability等等。还有一些是多个应用都会用到的基础模块,比如HBAPI、HBSNS 、HBFoundation(HB为公司名首字母)等等,可以建一个私有的git repo,然后加到podfile中,这样如果HBAPI有更新,只需要pod update一下就行了。

顺便说一下HBFoundation,这个git仓库中可以放一些自己写的所有应用基本上都会用到的小模块。如很多app都会有隔一段时间跳出一个求好评的alertView,就可以写一个HBRating类,这样需要使用该功能的app只需加上一句:[HBRating checkIfShouldPopupWithAppID:(NSInteger)appID]就行了。又比如app都有接受push notification的需求,可以写一个HBAPNS类,等等。

开发流程

在拿到设计图后,就可以针对设计图抽离出可复用的Classes/Views/Helpers,考虑一下某个效果的具体实现,使用合适的设计模式来避免大量的if/else嵌套,等等。不要一下子就钻到Sections中去实现页面效果和功能,初看起来可能会快一点,但只要有点复杂度的项目,这种做法到后来只会吃尽苦头,代码会变的越来越难维护。所以前期一定要做好充足的准备工作。

2015/7/11 posted in  Program

常用代码块整理

/* 常用代码块
 
 
1.copy:
@property (nonatomic,copy) NSString *<#string#>;
 
 
2.strong:
@property (nonatomic,strong) <#Class#> *<#object#>;

 
3.weak:
@property (nonatomic,weak) <#Class#> *<#object#>;

 
4.assign:
@property (nonatomic,assign) <#Class#> <#property#>;
 
 
5.delegate:
@property (nonatomic,weak) id<<#protocol#>> <#delegate#>;
 

6.block:
@property (nonatomic,copy) <#Block#> <#block#>;
 
 
7.mark:
#pragma mark <#mark#>

 
8.gmark:
#pragma mark - <#gmark#>
 
 
9.warning:
#warning <#message#>
 


 
10.ReUseCell:
 
 static NSString *rid=<#rid#>;
 
 <#Class#> *cell=[tableView dequeueReusableCellWithIdentifier:rid];
 
 if(cell==nil){
 
 cell=[[<#Class#> alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:rid];
 
 }
 
 return cell;
 
 
11.initObj:
 
 if(self=[super init]){
    <#init#>
 }
 
 return self;
 
12.dataFill:
 -(void)dataFill:(<#ModelClass#> *)<#model#>{
 
    <#code#>

 }
 
13.MainGCD:
 dispatch_async(dispatch_get_main_queue(), ^{
    <#code#>
 });

 
14.GlobalGCD:
 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    <#code#>
 });

15.AfterGCD:
 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    <#code to be executed after a specified delay#>
 });
 
16.OnceGCD:
 static dispatch_once_t onceToken;
 dispatch_once(&onceToken, ^{
    <#code to be executed once#>
 });
 
 
17.APPLICATION:
 [UIApplication sharedApplication]

 
18.NOTICENTER:
 [NSNotificationCenter defaultCenter]

 
17.DEFINEIOS6
#define ios6x ([[[UIDevice currentDevice] systemVersion] floatValue] < 7.0f)
 
18.NSLOG:
NSLog(@"<#content#>");
 
 
20.VIEWDIDLOAD
 -(void)viewDidLoad{
 
     [super viewDidLoad];
     
     <#code#>
 }

 
21.VIEWWILLAPPEAR
 -(void)viewWillAppear:(BOOL)animated{
     [super viewWillAppear:animated];
     <#code#>
 }
 
 
22.VIEWDIDAPPEAR
 -(void)viewDidAppear:(BOOL)animated{
 
     [super viewDidAppear:animated];
     
     <#code#>
 }
 
 
23.VIEWWILLDISAPPEAR
 -(void)viewWillDisappear:(BOOL)animated{
 
     [super viewWillDisappear:animated];
     
     <#code#>
 }
 
 
24.VIEWDIDDISAPPEAR
 -(void)viewDidDisappear:(BOOL)animated{
 
     [super viewDidDisappear:animated];
     
     <#code#>
 }
 
 
24.LAYOUTSUBVIEWS
 -(void)layoutSubviews{
 
     [super layoutSubviews];
     
     <#code#>
 }
 
 
25.AWAKEFROMNIB
 -(void)awakeFromNib{
 
     [super awakeFromNib];
     
     <#code#>
 }
 
26.LOADVIEW
 -(void)loadView{
 
    <#code#>
 }
 
27.INITFRAME
 -(instancetype)initWithFrame:(CGRect)frame{
 
     self = [super initWithFrame:frame];
     
     if(self){
     
     <#code#>
 
     }
 
     return self;
 }
 
 
28.INITCODER
 -(id)initWithCoder:(NSCoder *)aDecoder{
 
 self=[super initWithCoder:aDecoder];
 
     if(self){
     
         <#code#>
     
     }
 
 return self;
 }
 
29.weakSelf
 __weak typeof(self) weakSelf=self;

30.DEBUGSCROLLVIEW
 NSLog(@"\n\n scrollView的Debug信息: \n\n frame=%@,\n bounds=%@,\n contentSize=%@,\n contentOffset=%@,\n contentInset=%@.\n",NSStringFromCGRect(scrollView.frame),NSStringFromCGRect(scrollView.bounds),NSStringFromCGSize(scrollView.contentSize),NSStringFromCGPoint(scrollView.contentOffset),NSStringFromUIEdgeInsets(scrollView.contentInset));

31.CONSTRAINTS
 [<#view#> mas_makeConstraints:^(MASConstraintMaker *make) {
 <#make.#>
 }];
 
 
*/
2015/5/28 posted in  苹果开发 Program