代码规范
1. h文件代码
类功能说明注释。
属性说明注释。
方法说明注释。
类名加前缀,避免命名空间冲突.
2. m文件代码
- 属性:
- 自动存取:所有的属性都使用getter和setter.(静态,全局变量等不需要),在对象内部除了init,dealloc,set/get此类方法使用_property外,其他地方都使用self.property;
- 属性声明: 声明时,原子属性放到存储属性后; 如果是私有变量放在扩展(Extension)里面声明,遵守协议声明也最好放到扩展里面.
@interface RWTDetailViewController ()
@property (strong, nonatomic) ADBannerView *iAdView;
@property (copy, nonatomic) NSString *name;
@end
补充 1:关于_property和self.property:业内对两种使用方式一直都有争议,唐巧支持使用_property格式, 通过
[self setupProperty]
这种做法去初始化_property
.但是这与苹果的风格不太一致;
补充 2:遵守声明时的属性特质:不管你是重写的setter/getter方法,还是在初始化方法里面进行属性赋值.你依然必须遵守@property
声明中的属性特质.例如:
-(instances)initWithName:(NSString*)name {
if(self = [super init]) {
_name = [name copy];
}
return self;
}
补充3: Category和Extension:创建category类名格式应该是:
, Category主要用于为现有类添加新方法,在不修改原类的情况下扩展其他功能.目的是对类方法进行归类,便于分模块开发;
+Private.h
Extension其实就是匿名Category,比较特殊,可以用来声明新的私有方法和属性,不过实现还是在原类中.
补充 4: 属性的接口设计:尽量使用不可变对象, 如果允许外界改变,最好单独提供方法来修改; 如某些属性仅可用于对象内部修改,那么可在.h中声明为readonly, 而在.m中的扩展里面,继续声明为readwrite.这样外部不可改,内部还可继续使用setter方法,从而也可以触发KVO.(ps.如果想让代码更易阅读,可以把私有方法的声明写在扩展里面,不过很多人偷懒,都不写声明);
XCODE默认换行是4个空格,推 荐使用2个空格,让代码看起来更紧凑,
设置方法:xcode->preferences->Text Editing->Indentation->Indent width
多个回调模块,使用
{}
括号的对齐比使用 方法名中的:
对齐要便于阅读。
// blocks are easily readable
[UIView animateWithDuration:1.0 animations:^{
// something
} completion:^(BOOL finished) {
// something
}];
- 方法名: 多个参数名前的方法要有描述性的关键字,并且不要包含and字段.
- (void)setExampleText:(NSString *)text image:(UIImage *)image;
- (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag;
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;
补充:
细节:1. 如果返回新创建的对象,那么方法名首词应该是返回值类型; 2. 把表示参数类型的名词放在参数前; 3. 如果方法是要在当前对象上执行操作,那么就应该包含动词,若还需要参数,则在动词后加上一个或多个名词;4. 不要随意使用缩略的简称;
私有方法:加前缀,以便于区别面向外界的API,这样若要修改其名称或标签时,只需修改内部相关代码即可,不会担心影响外界.最好加下划线和p
,方便识别理解. 注意,最好不要单以_
做前缀,因为苹果就喜欢用下划线做私有方法前缀,所以要防止覆写系统方法.
- 常量: 常量需使用
static
与const
创建,而#define
通常用于定义便利方法. 命名规则:如果是在类内部使用,那么使用k
做前缀. 如果是要类外部也使用, 则要使用类名为前缀.
// 在.h文件中
UIKIT_EXTERN NSString * const RWTAboutViewControllerCompanyName;
//.m文件中中
NSString * const RWTAboutViewControllerCompanyName = @"RayWenderlich.com";
// 私有常量
static CGFloat const kThumbnailHeight = 50.0f;
使用
static const
的好处就是,这个常量带有类型信息.而#define
不会.
- 点语法: 点语法应该只被用于获取或者改变属性. 其他方式还是使用
[ ]
.
//正确
NSInteger arrayCount = [self.array count];
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;
//错误
NSInteger arrayCount = self.array.count;
[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;
- 字面变量: 尽量使用字面变量代替原方法:(注意1.原方法创建Array和Dictionary时末尾要加nil.而使用字面变量则不用.注意2.字面变量创建的集合都是不可变的,需要可变的要进行
mutableCopy
)
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone": @"Kate", @"iPad": @"Kamal", @"Mobile Web": @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingStreetNumber = @10018;
- 控制流:
8.1 开始:第一个{
要与if/else/switch/while控制流方法处于同一行,}
则需要另起一行。
if (!error) {
return success;
} // 括号不可省
8.2 枚举:用来表示:状态,选项.推荐使用代码提示中宏定义的NS_ENUM()
(ps.如果是C++编译,使用NS_OPTIONS
宏替换)枚举模块来创建,它可以提供更严谨的类型检测和代码补完功能.
typedef NS_ENUM(NSInteger, RWTLeftMenuTopItemType) {
RWTLeftMenuTopItemMain = 1 << 0,
RWTLeftMenuTopItemShows = 1 << 1,
RWTLeftMenuTopItemSchedule = 1 << 2
};
枚举后面的
= 1 << 0
是利用二进制位运算,可以方便的判断是否满足枚举的多个组合选项. 例:RWTLeftMenuTopItemMain | RWTLeftMenuTopItemShows
就是表示同时满足1和2;
8.3 switch:每一个case
分支下执行语句,如果只有一行,不加{}
; 如果多行, 使用{}
;
switch (condition) {
case 1:
// ...
break;
case 2: {
// ...
// Multi-line example using braces
break;
}
case 3:
// ...
break;
}
default
是非必须,要习惯不写,这是由于如果switch的条件是枚举,那么如果以后此枚举选项新增,而不使用default时,编译器会给我们提醒,方便处理.
8.4 三目运算符: 只有在确定能够促进代码整洁与清晰的前提下才使用.非BOOL类型变量必须要与其类型变量做出判断后才能使用。
NSInteger value = 5;
result = (value != 0) ? x : y;
BOOL isHorizontal = YES;
result = isHorizontal ? x : y;
- 布尔BOOL:声明:
@property (assign, getter=isEditable) BOOL editable;
,另外不要把对象直接和YES
进行比较,
// 一般对象 是否为nil
if (!someObject) {}
// NSString是否有值
if(string.length) {}
// 集合内部value是否为nil
if (dict[key] == [NSNull null]) {}
-
注释:
- 注释及时更新,或者直接删除;
- 尽量做到代码如注释。
代码换行 * 代码太长需要换行时,与首行要有两个空格间隔.设置:
xcode->preferences->Text Editing->Indentation->Line Wrapping 手动设置为2
self.productsRequest = [[SKProductsRequest alloc]
initWithProductIdentifiers:productIdentifiers];
- if扩展: 减少if嵌套,扁平化多个返回条件.
- (void)someMethod {
if (![someOther boolValue]) {
return;
}
//Do something important
}
// 错误处理时, if条件必须是error本身.
NSError *error;
if (![self trySomethingWithError:&error]) {
// Handle Error
}
// 不好的写法:
NSError *error;
[self trySomethingWithError:&error];
if (error) {
// Handle Error
}
ps. 苹果新语言Swift的
if guard
语句就是为了解决if嵌套问题.
3.控制器结构:
#pragma mark - DataRequest
#pragma mark - DefaultSetting/Intial
#pragma mark - View生命周期
#pragma mark -Target/Event response
#pragma mark - Private (功能性方法一般放到category中,ViewController基本上是大部分业务的载体,尽量不放与业务无关的代码)
#pragma mark - Deletate/DataSource
#pragma mark - Setter/Getter
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {...}
#pragma mark - NSObject/Override
- (NSString *)description {...}
如有需要,可以继续在其中划分子代码区.
-(void)loadView {
// 第一次加载控制器时会调用.
// 只有需要使用自定义view才重写,且不需要调用[super loadView].
self.view = self.customView;
}
- (void)viewDidLoad
{
// 此方法中只addSubview,属性初始化都放到Getter去做, 尽量简洁。
self.view.backgroundColor = [UIColor whiteColor];
[self.view addSubview:self.firstTableView];
[self.view addSubview:self.secondTableView];
// 如果是使用autolayout设置constraints.则在这里自定义一个方法调用.例:
[self layoutSubviewConstraints];
}
- (void)layoutSubviewConstraints
{
[self.view addConstraints:xxxConstraints];
[self.view addConstraints:yyyConstraints];
[self.view addConstraints:zzzConstraints];
}
- (void)viewWillAppear
{
//在此方法只中布局子控件的frame,
//如果是autolayout就在viewdidload add完subview之后开个方法去单独写constraints。
}
- (void)viewDidAppear
{
//此方法中添加监听/Notification等
}
#pragma mark - getters and setters
- (UIView *)label {
if (_label == nil) {
_label = [[UILabel alloc] init];
_label.text = @"1234";
_label.font = [UIFont systemFontOfSize:12];
... ...
//label的子view,还有gestureRecognizer等也是可以放到getter中的,getter相当于是它的工厂方法.
}
return _label;
}
4. 自定义View
- 重写initwithFrame方法,此方法中创建-添加-记录子控件,并且可以设置不需要数据的和非frame的格式属性.
- 有些行为需要通知外部,对象间通信的需求等 使用代理或通知或block.
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self addOwnViews];
[self configOwnViews];
}
return self;
}
- (void)addOwnViews {
// 添加子控件
}
- (void)configOwnViews {
// 配置子控件
}
//在外界设置好self的frame后调用,来设置子控件frame.
- (void)relayoutFrameOfSubViews {
// 设置frame,
}
// 模型数据set方法
- (void)setModel(Model*)model
{
_model = model;
...
// 设置动态属性
}
// 补充: 重新布局子控件frame.
- (void)layoutSubviews
{
[super layoutSubviews]
//1. 每次addSubview和frame变动就会调用.
//2. ScrollView滚动触发.
//3. Screen旋转
//4. 手动调用 setNeedsLayout.(异步,非即时的) layoutIfNeeded:如果有需要刷新的标记,立即刷新.
PS.所以若有立即刷新, 要同时调用这两个方法, (视图第一次显示时默认被标记,此时只需调用ifNeed.)
所以: 如果你想强制更新布局,你可以调用setNeedsLayout方法;如果你想立即显示你的views,你需要调用layoutIfNeeded方法。
}
类似的drawRect和setNeedsDisplay方法; drawRect是在视图被真正显示的时候,如果layer的contents没有内容,就会被调用.
即: layoutSubviews是更新自己子视图们的frame;而drawRect是创建自己的视图内容;
5. 补充:
1. 类簇模式.
类簇模式可以灵活应对多个类,将他们的实现细节隐藏在同一个抽象基类后面,保持界面整洁,系统. 例如 + (UIButton *)buttonWithType:(UIButtonType) type
方法会根据传入的类型,来返回不同的button;
类簇底层实现很简单,就是枚举+switch
,根据type实现不同的样式;
类簇注意的是:
1.要创建的子类应该继承自抽象类.
2.子类应该定义自己的数据存储方式.
3.子类应该覆写超类文档中指明的覆写方法,例如,要编写系统NSArray的子类,就要实现count及"objectAtIndex:"方法.
2. 使用自动释放池来降低内存峰值
及时释放大量创建的临时对象,避免内存过高;
NSArray *databaseRecodrs;
NSMutableArray *people = [NSMutableArray new];
for (NSDictionary *record in databaseRecords) {
@autoreleasepool {
EocPerson *person = [[EOCPerson alloc] initWithRecordLrecord];
[people addObject:person];
}
}
3. 使用block回调代替委托回调.使代码更紧凑.
#import
@class EOCNetworkFetcher;
typedef void (^EOCNetworkFetcherCompletionHandler) (NSData *data, NSError *error);
#interface EOCNetworkFetcher : NSObject
- (instance)initWithURL:(NSURL*)url;
- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completionHandler;
调用API的时候:
EOCNetworkFetcher *fetcher = [EOCNetworkFetcher alloc] initWithURL:url];
[fetcher startWithCompletionHandler:^(NSData *data, NSError *error) {
if (error) {
//handle failure
} else {
// handle success
}
}];