iOS 编程规范

366 阅读17分钟

一、命名规范

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

驼峰命名法:
  • 大驼峰式命名:每个单词的首字母都采用大写字母;(类、协议)
  • 大驼峰式命名:第一个单词以小写字母开始,后面的单词的首字母全部大写;(对象、局部变量)

1. 类名

大驼峰式命名:每个单词的首字母都采用大写字母;

MFHomePageViewController

类的名称应该以三个大写字母为前缀;创建子类的时候,应该把代表子类特点的部分放在前缀和父类名的中间

推荐这样写:

//父类
ZOCSalesListViewController

//子类
ZOCDaySalesListViewController
ZOCMonthSalesListViewController

2. 私有变量

  • 私有变量放在 .m 文件中声明
  • 以 _ 开头,第一个单词首字母小写,后面的单词的首字母全部大写;

NSString *_somePrivateVariable

3. property变量

  • 小驼峰式命名:第一个单词以小写字母开始,后面的单词的首字母全部大写;
@property (nonatomic, readwrite, copy) NSString *userNameStr;
  • 变量的名称必须同时包含功能与类型
UIButton *addBtn //添加按钮
UILabel *nameLbl //名字标签
NSString *addressStr//地址字符串

4. 宏和常量命名

4.1 对于宏定义的常量

#define 预处理定义的常量全部大写,单词间用 _ 分隔;

//宏定义的常量
#define ANIMATION_DURATION    0.3
4.2 对于类型常量
  • 局部类型常量:对于局限于某编译单元(实现文件)的常量,以字符k开头,例如kAnimationDuration,且需要以static const修饰;
  • 常量以相关类名作为前缀;
//局部类型常量
推荐:
static const NSTimeInterval ZOCSignInViewControllerFadeOutAnimationDuration = 0.4;

不推荐:
static const NSTimeInterval fadeOutTime = 0.4;
  • 外部可见类型常量:对于定义于类头文件的常量,外部可见,则以定义该常量所在类的类名开头,例如EOCViewClassAnimationDuration, 仿照苹果风格,在头文件中进行extern声明,在实现文件中定义其值;
//外部可见类型常量
//EOCViewClass.h
extern const NSTimeInterval EOCViewClassAnimationDuration;
extern NSString *const EOCViewClassStringConstant;  //字符串类型

//EOCViewClass.m
const NSTimeInterval EOCViewClassAnimationDuration = 0.3;
NSString *const EOCViewClassStringConstant = @"EOCStringConstant";
4.3 建议使用类型常量,不建议使用#define预处理命令:

首先比较一下这两种声明常量的区别:

  • 预处理命令:简单的文本替换,不包括类型信息,并且可被任意修改;
  • 类型常量:包括类型信息,并且可以设置其使用范围,而且不可被修改;

使用预处理虽然能达到替换文本的目的,但是本身还是有局限性的:

  • 不具备类型信息;
  • 可以被任意修改;
4.4 对外公开某个常量:

如果我们需要发送通知,那么就需要在不同的地方拿到通知的“频道”字符串(通知的名称),那么显然这个字符串是不能被轻易更改,而且可以在不同的地方获取。这个时候就需要定义一个外界可见的字符串常量;

==推荐这样写:==

//头文件
extern NSString *const ZOCCacheControllerDidClearCacheNotification;

//实现文件
static NSString * const ZOCCacheControllerDidClearCacheNotification = @"ZOCCacheControllerDidClearCacheNotification";

== 不推荐这样写:==

#define CompanyName @"Apple Inc." 
#define magicNumber 42 

5. Enum

  • Enum类型的命名与类的命名规则一致;
  • Enum中枚举内容的命名需要以该Enum类型名称开头;
  • NS_ENUM定义通用枚举,NS_OPTIONS定义位移枚举
typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
    UIViewAnimationTransitionNone,
    UIViewAnimationTransitionFlipFromLeft,
    UIViewAnimationTransitionFlipFromRight,
    UIViewAnimationTransitionCurlUp,
    UIViewAnimationTransitionCurlDown,
};

typedef NS_OPTIONS(NSUInteger, UIControlState) {
    UIControlStateNormal       = 0,
    UIControlStateHighlighted  = 1 << 0,
    UIControlStateDisabled     = 1 << 1,
};

6. Delegate

  • delegate做后缀,如<UIScrollViewDelegate>;
  • optional修饰可以不实现的方法,用required修饰必须实现的方法;
  • 当你的委托的方法过多, 可以拆分数据部分和其他逻辑部分, 数据部分用dataSource做后缀. 如<UITableViewDataSource>;
  • 使用did和will通知Delegate已经发生的变化或将要发生的变化;
  • 类的实例必须为回调方法的参数之一:
    • 回调方法的参数只有类自己的情况,方法名要符合实际含义;
    • 回调方法存在两个以上参数的情况,以类的名字开头,以表明此方法是属于哪个类的;
@protocol UITableViewDataSource<NSObject>

@required

//回调方法存在两个以上参数
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;

@optional

//回调方法的参数只有类自己
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;              // Default is 1 if not implemented
@protocol UITableViewDelegate<NSObject, UIScrollViewDelegate>

@optional

//使用`did`和`will`通知`Delegate`
- (nullable NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath;

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

7. 方法

  • 方法名用小驼峰式命名;
  • 方法名不要使用new作为前缀;
  • 不要使用and来连接属性参数,如果方法描述两种独立的行为,使用and来串接它们;
  • 方法实现时,如果参数过长,则令每个参数占用一行,以冒号对齐;
  • 一般方法不使用前缀命名,私有方法可以使用统一的前缀来分组和辨识;
  • 方法名要与对应的参数名保持高度一致;
  • 表示对象行为的方法、执行性的方法应该以动词开头;
  • 返回性的方法应该以返回的内容开头,但之前不要加get,除非是间接返回一个或多个值;
  • 可以使用情态动词(动词前面can、should、will等)进一步说明属性意思,但不要使用dodoes,因为这些助动词没什么实际意义。也不要在动词前使用副词或形容词修饰;
//不要使用 and 来连接属性参数
- (int)runModalForDirectory:(NSString *)path file:(NSString *)name types:(NSArray *)fileTypes;    //推荐
- (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes;    //反对

//表示对象行为的方法、执行性的方法
- (void)insertModel:(id)model atIndex:(NSUInteger)atIndex;
- (void)selectTabViewItem:(NSTableViewItem *)tableViewItem

//返回性的方法
- (instancetype)arrayWithArray:(NSArray *)array;

//参数过长的情况
- (void)longMethodWith:(NSString *)theFoo
                  rect:(CGRect)theRect
              interval:(CGFloat)theInterval
{
   //Implementation
}

//不要加get
- (NSSize) cellSize;  //推荐
- (NSSize) getCellSize;  //反对

//使用情态动词,不要使用do或does
- (BOOL)canHide;  //推荐
- (BOOL)shouldCloseDocument;  //推荐
- (BOOL)doesAcceptGlyphInfo;  //反对

8. 系统常用类作实例变量声明时加入后缀

类型 后缀
UIViewController VC
UIView View
UILabel Lbl
UIButton Btn
UIImage Img
UIImageView ImagView
NSArray Array
NSMutableArray Marray
NSDictionary Dict
NSMutableDictionary Mdict
NSString Str
NSMutableString Mstr
NSSet Set
NSMutableSet Mset

9. 泛型

建议在定义NSArray和NSDictionary时使用泛型,可以保证程序的安全性:

NSArray<NSString *> *testArr = [NSArray arrayWithObjects:@"Hello", @"world", nil];

NSDictionary<NSString *, NSNumber *> *dic = @{@"key":@(1), @"age":@(10)};

10. 命名-Category

在写添加类别方法时,必须采用前缀名_后连接对应方法名的方式来进行添加。这样就有效的避免了覆盖掉系统原有或新增方法,导致意想不到的问题放生;

- (void)JS_methodName

11. 命名-通知

苹果公司推荐在通知名称中包含下列元素:

  • 相关类的名称;
  • 单词 Did 或 Will;
  • 实现唯一性的名称成分;
  • 单词 Notification;

这些指导原则旨在方便创建带自我描述的,具有唯一性的通知名称;

NSString *nName = @"ApplicationDidHandleGreetingNotification";

二、注释

优秀的代码大部分是可以自描述的,我们完全可以用代码本身来表达它到底在干什么,而不需要注释的辅助;

但并不是说一定不能写注释,有以下三种情况比较适合写注释:

  • 公共接口(注释要告诉阅读代码的人,当前类或方法能实现什么功能);
  • 涉及到比较深层专业知识的代码(注释要体现出实现原理和思想);
  • 容易产生歧义的代码(但是严格来说,容易让人产生歧义的代码是不允许存在的);

除了上述这三种情况,如果别人只能依靠注释才能读懂你的代码的时候,就要反思代码出现了什么问题;

最后,对于注释的内容,相对于“做了什么”,更应该说明“为什么这么做”;

1. import注释

如果有一个以上的import语句,就对这些语句进行分组,每个分组的注释是可选的;

// Frameworks
#import <QuartzCore>;

// Models
#import "NYTUser.h"

// Views
#import "NYTButton.h"
#import "NYTUserView.h"

2. 属性注释

@property (nonatomic, readwrite, strong) UIView *headerView;  //注释    

3. 声明与实现注释

每个接口,类别,协议的声明都应该有个伴随的注释,来描述他的作用以及他如何融入整体环境;

下面以方法为例:

一个函数(方法)必须有一个字符串文档来解释,除非它:

  • 非公开,私有函数;
  • 很短;
  • 显而易见;

而其余的,包括公开接口,重要的方法,分类,以及协议,都应该伴随文档(注释):

  • 以/开始;
  • 第二行是总结性的语句;
  • 第三行永远是空行;
  • 在与第二行开头对齐的位置写剩下的注释;
/* 
 This method is called when the view controller's trait collection is changed by its parent.
 
 If you override this method, you should either call super to propagate the change to children or manually forward the change to children.
 */

方法实现的注释使用Xcode自带注释快捷键:Commond+option+/

/// <#Description#>
/// @param touches <#touches description#>
/// @param event <#event description#>
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
}

4. 代码块注释

单行的用//+空格开头,多行的采用/* */注释;

5. TODO 标记未完成或不尽如人意

使用//TODO:说明 标记一些未完成的或完成的不尽如人意的地方

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    //TODO:增加初始化
    return YES;
}

6. 文件注释

文件最前面的注释,是保留了工程自动生成的注释,但是需要进行如下修改:文件作者改为个人的名字,公司名为Insigma Hengtian software Ltd. 如:

//
//  ViewController.m
//  项目工程名
//
//  Created by 文件作者 on 14-5-7.
//  Copyright (c) 2014年 公司名. All rights reserved.
//

可以有选择的在一个文件开头写一段关于内容的描述;

7. 类、协议、结构体注释

/*
 @class HTServerDatamanager
 @abstract 异步连接服务器管理类
 @discussion 异步请求服务器,接收到响应后,通过回调把数据回传到对象
 */

三、格式化规范

1. 指针*位置

定义一个对象时,指针*靠近变量;

NSString *userName;

2. 方法

2.1 在 - 、+和 返回值之间留一个空格,方法名和第一个参数之间不留空格;
- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index;
2.2 方法实现时,如果参数过长,则令每个参数占用一行,以冒号对齐;
- (void)doSomethingWith:(NSString *)theFoo
                   rect:(CGRect)theRect
               interval:(CGFloat)theInterval
{
   //Implementation
}
2.3 如果方法名比参数名短,则每个参数占用一行,至少缩进4个字符,且为垂直对齐(非冒号对齐)
- (void)short:(NSString *)theFoot
    longKeyword:(CGRect)theRect
    evenLongerkeyword:(float)theInterval
{
}
2.3 方法名前缀
  • 刷新视图的方法名要以refresh为首;
  • 更新数据的方法名要以update为首;

==推荐这样写:==

- (void)refreshHeaderViewWithCount:(NSUInteger)count;
- (void)updateDataSourceWithViewModel:(ViewModel*)viewModel;

3. 代码缩进

不要在工程里使用 Tab 键,使用空格来进行缩进。在 Xcode > Preferences > Text Editing 将 Tab 和自动缩进都设置为 4 个空格;

Method与Method之间空一行;

- (void)sampleMethod1;

- (void)sampleMethod2;

一元运算符与变量之间没有空格、二元运算符与变量之间必须有空格;

!bValue

fLength = fWidth * 2;

4. 对method进行分组

使用#pragma mark -对method进行分组;

#pragma mark - Life Cycle Methods
- (instancetype)init
- (void)dealloc

- (void)viewWillAppear:(BOOL)animated
- (void)viewDidAppear:(BOOL)animated
- (void)viewWillDisappear:(BOOL)animated
- (void)viewDidDisappear:(BOOL)animated

#pragma mark - Override Methods

#pragma mark - Intial Methods

#pragma mark - Network Methods

#pragma mark - Target Methods

#pragma mark - Public Methods

#pragma mark - Private Methods

#pragma mark - UITableViewDataSource  
#pragma mark - UITableViewDelegate  

#pragma mark - Lazy Loads

#pragma mark - NSCopying  

#pragma mark - NSObject  Methods

5. 大括号写法

左括号跟在第一行后边,并与最后一个单词空一个空格;

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    if () {
    }
}

6. Protocals

6.1 Protocals声明中类型标识符、代理名称、尖括号间不留空格;
id<MyProtocalDelegate> delegate;
6.2 在类声明中包含多个protocal,每个protocal占用一行,缩进2个字符;(也可以选择6.3的格式)
@interface CustomBackButtonViewController () <UITextFieldDelegate,
  MyProtocalDelegate,
  UITabBarControllerDelegate,
  UITabBarDelegate>
6.3 也可以与第一个对齐,保持清晰易读
@interface ShopViewController () <UIGestureRecognizerDelegate,
                                  HXSClickEventDelegate,
                                  UITableViewDelegate,
                                  UITableViewDataSource>

7. UIViewController.m的文件布局,不同变量和方法的先后顺序

7.1 导入头文件#import "LocationCustomButton.h"

最开始是导入需要使用到的其他class头文件,头文件按类型分类

// controller
#import “TextSelectionViewController.h"
// model
#import "PayPasswordUpdateModel.h"
#import "WalletModel.h"
// views
#import "PayPasswordAlertView.h"
7.2 添加宏定义

添加宏定义,将在文件中需要使用到的常量,字符串等用宏定义;

#define KEY_BANK_TAIL @“bank_tail"

7.3 属性定义

property的属性定义。不用将需要使用的methods声明,在iOS7后Methods已经不需要先声明再使用了;

@property (nonatomic, strong) NSArray *bankCardList;

7.4 添加生命周期的方法

#pragma mark - Life Cycle

- (void)viewDidLoad;
- (void)viewWillAppear:(BOOL)animated;
- (void)viewDidAppear:(BOOL)animated;
- (void)viewWillDisappear:(BOOL)animated;
- (void)viewDidDisappear:(BOOL)animated;
- (void)didReceiveMemoryWarning;
- (void)dealloc;
7.5 重载父类的方法

#pragma mark - override

7.6 初始化的方法

#pragma mark - Intial Methods

- (void)initialNavigationBar;
- (void)initialTableView;
7.7 点击事件或通知事件

#pragma mark - Target Methods

7.8 代理事件

#pragma mark - UITextFieldDelegate Delegate的事件,将所有的delegate放在同一个pragma下;

7.9 所有的property使用懒加载

#pragma mark - Setter Getter Methods 所有的property使用懒加载,并将setter或getter放在底部,不影响业务代码的阅读;

7.10 私有方法

#pragma mark - private method

8. 每一行的最大长度

同样的,在 Xcode > Preferences > Text Editing > Page guide at column: 中将最大行长设置为80,过长的一行代码将会导致可读性问题;

9. @public 和 @private 标记符

@public 和 @private 标记符应该以一个空格来进行缩进:

@interface MyClass : NSObject {
 @public
  ...
 @private
  ...
}
@end

四、编码规范

1. if 语句

1.1 须列出所有分支(穷举所有的情况),而且每个分支都须给出明确的结果;

==推荐这样写:==

var hintStr;
if (count < 3) {
  hintStr = "Good";
} else {
  hintStr = "";
}

==不推荐这样写:==

var hintStr;
if (count < 3) {
 hintStr = "Good";
}
1.2 不要使用过多的分支,要善于使用return来提前返回错误的情况,把最正确的情况放到最后返回;

==推荐这样写:==

if (!user.UserName) return NO;
if (!user.Password) return NO;
if (!user.Email) return NO;

return YES;

==不推荐这样写:==

BOOL isValid = NO;
if (user.UserName)
{
    if (user.Password)
    {
        if (user.Email) isValid = YES;
    }
}
return isValid;
1.3 条件过多,过长的时候应该换行。条件表达式如果很长,则需要将他们提取出来赋给一个BOOL值,或者抽取出一个方法;

==推荐这样写:==

if (condition1 && 
    condition2 && 
    condition3 && 
    condition4) {
  // Do something
}
BOOL finalCondition = condition1 && condition2 && condition3 && condition4
if (finalCondition) {
  // Do something
}
if ([self canDelete]){
  // Do something
}

- (BOOL)canDelete
{
    BOOL finalCondition1 = condition1 && condition2
    BOOL finalCondition2 =  condition3 && condition4

    return condition1 && condition2;
}

==不推荐这样写:==

if (condition1 && condition2 && condition3 && condition4) {
  // Do something
}
1.4 每个分支的实现代码都须被大括号包围;

==推荐:==

if (!error) {
  return success;
}

==不推荐:==

if (!error)
    return success;

2. for 语句

不可在for循环内修改循环变量,防止for循环失去控制;

for (int index = 0; index < 10; index++){
   ...
   logicToChange(index)
}

3. Switch语句

3.1 每个分支都必须用大括号括起来;

==推荐:==

switch (integer) {  
  case 1:  {
    // ...  
   }
    break;  
  case 2: {  
    // ...  
    break;  
  }  
  default:{
    // ...  
    break; 
  }
}
3.2 使用枚举类型时,不能有default分支, 除了使用枚举类型以外,都必须有default分支;
RWTLeftMenuTopItemType menuType = RWTLeftMenuTopItemMain;  
switch (menuType) {  
  case RWTLeftMenuTopItemMain: {
    // ...  
    break; 
   }
  case RWTLeftMenuTopItemShows: {
    // ...  
    break; 
  }
  case RWTLeftMenuTopItemSchedule: {
    // ...  
    break; 
  }
}

在Switch语句使用枚举类型的时候,如果使用了default分支,在将来就无法通过编译器来检查新增的枚举类型了;

4. 函数

4.1 一个函数只做一件事(单一原则)

每个函数的职责都应该划分的很明确(就像类一样);

==推荐:==

- (void)dataConfiguration;
- (void)viewConfiguration;

==不推荐:==

- (void)dataConfiguration
{   
   ...
   [self viewConfiguration];
}
4.2 对于有返回值的函数(方法),每一个分支都必须有返回值
- (NSString *)dataConfiguration
{
    if(condition1){
        return count1
    }else if(condition2){
        return count2
    }else{
       return defaultCount
    } 
}

==不推荐:==

- (NSString *)dataConfiguration
{
    if(condition1){
        return count1
    }else if(condition2){
        return count2
    }
}
4.3 对输入参数的正确性和有效性进行检查,参数错误立即返回;

==推荐:==

void function(param1,param2)
{
      if(param1 is unavailable){
           return;
      }

      if(param2 is unavailable){
           return;
      }

     //Do some right thing
}
4.4 如果在不同的函数内部有相同的功能,应该把相同的功能抽取出来单独作为另一个函数;

==推荐:==

void basicConfig() {
  a();
  b();
}

void logic1() {
  basicConfig();
  c();
}

void logic2() {
  basicConfig();
  d();
}

==不推荐:==

void logic() {
  a();
  b();
  if (logic1 condition) {
    c();
  } else {
    d();
  }
}
4.5 将函数内部比较复杂的逻辑提取出来作为单独的函数;

一个函数内的不清晰(逻辑判断比较多,行数较多)的那片代码,往往可以被提取出去,构成一个新的函数,然后在原来的地方调用它这样你就可以使用有意义的函数名来代替注释,增加程序的可读性;

原来代码:举一个发送邮件的例子;

openEmailSite();
login();

writeTitle(title);
writeContent(content);
writeReceiver(receiver);
addAttachment(attachment);

send();

优化:中间的部分稍微长一些,我们可以将它们提取出来:

void writeEmail(title, content,receiver,attachment)
{
  writeTitle(title);
  writeContent(content);
  writeReceiver(receiver);
  addAttachment(attachment); 
}

优化后:

openEmailSite();
login();
writeEmail(title, content,receiver,attachment)
send();
4.6 方法调用沿用声明方法的习惯;

在同一行

[self doSomethingWith:@"test" rect:self.view.frame interval:1.0f];

或冒号对其

[self doSomethingWith:@"test"
                 rect:self.view.frame
             interval:1.0f];

5. 除了init和dealloc方法,建议都使用点语法访问属性

使用点语法的好处:

setter:

  • setter会遵守内存管理语义(strong, copy, weak);
  • 通过在内部设置断点,有助于调试bug;
  • 可以过滤一些外部传入的值;
  • 捕捉KVO通知;

getter:

  • 允许子类化;
  • 通过在内部设置断点,有助于调试bug;
  • 实现懒加载;

==注意:==

  1. 懒加载的属性,必须通过点语法来读取数据。因为懒加载是通过重写getter方法来初始化实例变量的,如果不通过属性来读取该实例变量,那么这个实例变量就永远不会被初始化;

  2. 在init和dealloc方法里面使用点语法的后果是:因为没有绕过setter和getter,在setter和getter里面可能会有很多其他的操作。而且如果它的子类重载了它的setter和getter方法,那么就可能导致该子类调用其他的方法;

6. 尽量使用不可变对象

建议尽量把对外公布出来的属性设置为只读,在实现文件内部设为读写。具体做法是:

  • 在头文件中,设置对象属性为readonly;
  • 在实现文件中设置为readwrite;

7. CGRect 使用

当需要访问 CGRect 中某个成员时, 应该使用 CGGeometry 函数来直接访问。 而不是使用 .语法来获取;

推荐:

CGRect frame = self.view.frame;

CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);

不推荐:

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;

8. 把类的实现代码分散到便于管理的多个分类中

一个类可能会有很多公共方法,而且这些方法往往可以用某种特有的逻辑来分组。我们可以利用Objecctive-C的分类机制,将类的这些方法按一定的逻辑划入几个分区中;

先看一个没有使用无分类的类:

#import <Foundation/Foundation.h>

@interface EOCPerson : NSObject

@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSArray *friends;

- (id)initWithFirstName:(NSString*)firstName andLastName:(NSString*)lastName;

/* Friendship methods */
- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;
- (BOOL)isFriendsWith:(EOCPerson*)person;

/* Work methods */
- (void)performDaysWork;
- (void)takeVacationFromWork;

/* Play methods */
- (void)goToTheCinema;
- (void)goToSportsGame;

@end

分类之后:

#import <Foundation/Foundation.h>

@interface EOCPerson : NSObject

@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSArray *friends;

- (id)initWithFirstName:(NSString*)firstName

andLastName:(NSString*)lastName;

@end

@interface EOCPerson (Friendship)

- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;
- (BOOL)isFriendsWith:(EOCPerson*)person;

@end

@interface EOCPerson (Work)

- (void)performDaysWork;
- (void)takeVacationFromWork;

@end

@interface EOCPerson (Play)

- (void)goToTheCinema;
- (void)goToSportsGame;

@end

其中,FriendShip分类的实现代码可以这么写:


// EOCPerson+Friendship.h
#import "EOCPerson.h"

@interface EOCPerson (Friendship)

- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;
- (BOOL)isFriendsWith:(EOCPerson*)person;

@end

// EOCPerson+Friendship.m
#import "EOCPerson+Friendship.h"

@implementation EOCPerson (Friendship)

- (void)addFriend:(EOCPerson*)person 
{
   /* ... */
}

- (void)removeFriend:(EOCPerson*)person 
{
   /* ... */
}

- (BOOL)isFriendsWith:(EOCPerson*)person 
{
   /* ... */
}

@end

通过分类机制,可以把类代码分成很多个易于管理的功能区,同时也便于调试。因为分类的方法名称会包含分类的名称,可以马上看到该方法属于哪个分类中;

利用这一点,我们可以创建名为Private的分类,将所有私有方法都放在该类里。这样一来,我们就可以根据private一词的出现位置来判断调用的合理性,这也是一种编写“自我描述式代码(self-documenting)”的办法;

9. 尽量避免使用c的基本类型

  int      -> NSInteger
  unsigned -> NSUInteger
  float    -> CGFloat
  动画时间  -> NSTimeInterval
  enum     -> NS_ENUM