Object-c 代码规范

  Objective-C 编码规范

  

前言

    Objective-C 是 C 语言的扩展,增加了动态类型和面对对象的特性。它被设计成具有易读易用的,支持复杂的面向对象设计的编程语言。它是 Mac OS X 以及 iPhone 的主要开发语言。本文档目标是统一开发人员的编码风格,使项目容易被理解以及后期维护。

1留白和格式

1.1空格 VS 制表符

   【建议】使用空格进行缩进。

   不要在工程里使用Tab键,使用空格来进行缩进。

通过设置 Xcode > Preferences > Text Editing 将Tab和自动缩进都设置为4个空格。

  • 类方法声明在方法类型与返回类型之间要有空格。

// 糟糕

-(void)methodName:(NSString *)string; 

// OK

- (void)methodName:(NSString *)string;


  • 条件判断的括号内侧不应有空格。

// 糟糕

if ( a < b ) { 

// something

// OK

if (a < b) {

 // something

}

  • 关系运算符(如  >= 、 != )和逻辑运算符(如  && 、 || )两边要有空格。

// OK

(someValue > 100)? YES : NO 

// OK

(items)?: @[]

   二元算数运算符两侧是否加空格不确定,根据情况自己定。一元运算符与操作数之前没有空格。 

多个参数逗号后留一个空格(这也符合正常的西文语法)。 


1.2 花括号

【建议】方法大括号和其他大括号(if/else/switch/while 等.)总是在同一行语句打开但在新行中关闭。


if (user.isHappy) {

    //Do something

else {

    //Do something else

}


1.3列数

【建议】尽量让你的代码保持在 80 列之内。

    通过设置 Xcode > Preferences > Text Editing > Show page guide,来使越界更容易被发现。

1.4 方法的声明和定义


   【建议】 +/- 和返回类型之间须使用一个空格,参数列表中只有参数之间可以有空格。

  例:

+ (NSDate *)dateFromString:(NSString *)string{

    NSDateFormatter *formatter = [[NSDateFormatter alloc]init];

    [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];

    NSDate *date = [formatter dateFromString:string];

    [formatter release];

    return date;

}

    星号前的空格是可选的。当写新的代码时,要与先前代码保持一致。如果一行有非常多的参数,更好的方式是将每个参数单独拆成一行。如果使用多行,将每个参数前的冒号对齐。

- (void)doSomethingWith:(GTMFoo *)theFoo

                     rect:(NSRect)theRect

                 interval:(float)theInterval {

  ...

}


    当第一个关键字比其它的短时,保证下一行至少有 4 个空格的缩进。这样可以使关键字垂直对齐,而不是使用冒号对齐:


- (void)short:(GTMFoo *)theFoo

    longKeyword:(NSRect)theRect

    evenLongerKeyword:(float)theInterval {

  ...

}


1.5 方法调用

  【建议】方法调用应尽量保持与方法声明的格式一致。当格式的风格有多种选择时,新的代码要与已有代码保持一致。

调用时所有参数应该在同一行:

[myObject doFooWith:arg1 name:arg2 error:arg3];

或者每行一个参数,以冒号对齐:

   [myObject doFooWith:arg1

                    name:arg2

               error:arg3];

方法定义与方法声明一样,当关键字的长度不足以以冒号对齐时,下一行都要以四个空格进行缩进。

  [myObj short:arg1

    longKeyword:arg2

        evenLongerKeyword:arg3];


1.6 协议名

  【建议】类型标识符和尖括号内的协议名之间,不能有任何空格。

这条规则适用于类声明、实例变量以及方法声明。例如:

@interface MyProtocoledClass : NSObject<NSWindowDelegate> {

 @private

  id<MyFancyDelegate> delegate_;

}

- (void)setDelegate:(id<MyFancyDelegate>)aDelegate;

@end

@public 和 @private 访问修饰符应该以一个空格缩进。

1.7 块(闭包)

  【建议】块(block)适合用在 target/selector 模式下创建回调方法时,因为它使代码更易读。块中的代码应该缩进 4 个空格。


取决于块的长度,下列都是合理的风格准则:

  • 如果一行可以写完块,则没必要换行。
  • 如果不得不换行,关括号应与块声明的第一个字符对齐。

    块内的代码须按 4 空格缩进。

  • 如果块太长,比如超过 20 行,建议把它定义成一个局部变量,然后再使用该变量。
  • 如果块不带参数,^{ 之间无须空格。如果带有参数,^( 之间无须空格,但 ) { 之间须有一个空格。
  • 块内允许按两个空格缩进,但前提是和项目的其它代码保持一致的缩进风格。


// The entire block fits on one line.

[operation setCompletionBlock:^{ [self onOperationDone]; }];


[operation setCompletionBlock:^{

    [self.delegate newDataAvailable];

}];



dispatch_async(fileIOQueue_, ^{

    NSString* path = [self sessionFilePath];

    if (path) {

      // ...

    }

});



[[SessionService sharedService]

    loadWindowWithCompletionBlock:^(SessionWindow *window) {

        if (window) {

          [self windowDidLoad:window];

        } else {

          [self errorLoadingWindow];

        }

    }];


// An example where the parameter wraps and the block declaration does

// not fit on the same line as the name.

[[SessionService sharedService]

    loadWindowWithCompletionBlock:

        ^(SessionWindow *window) {

            if (window) {

              [self windowDidLoad:window];

            } else {

              [self errorLoadingWindow];

            }

        }];


// Large blocks can be declared out-of-line.

void (^largeBlock)(void) = ^{

    // ...

};

[operationQueue_ addOperationWithBlock:largeBlock];



1.8 枚举类型


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


typedef NS_ENUM(NSInteger, RWTLeftMenuTopItemType) {

  RWTLeftMenuTopItemMain,

  RWTLeftMenuTopItemShows,

  RWTLeftMenuTopItemSchedule

};

1.9 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;

}






2命名

    对于易维护的代码而言,命名规则非常重要。Objective-C 的方法名往往十分长,但代码块读起来就像散文一样,不需要太多的代码注释。

当编写纯粹的 Objective-C 代码时,我们基本遵守标准的 Objective-C naming rules,这些命名规则可能与 C++ 风格指南中的大相径庭。例如,Google 的 C++ 风格指南中推荐使用下划线分隔的单词作为变量名,而(苹果的)风格指南则使用驼峰命名法,这在 Objective-C 社区中非常普遍。

任何的类、类别、方法以及变量的名字中都使用全大写的 首字母缩写。这遵守了苹果的标准命名方式,如 URL、TIFF 以及 EXIF。

当编写 Objective-C++ 代码时,事情就不这么简单了。许多项目需要实现跨平台的 C++ API,并混合一些 Objective-C、Cocoa 代码,或者直接以 C++ 为后端,前端用本地 Cocoa 代码。这就导致了两种命名方式直接不统一。


我们的解决方案是:编码风格取决于方法/函数以哪种语言实现。如果在一个 @implementation 语句中,就使用 Objective-C 的风格。如果实现一个 C++ 的类,就使用 C++ 的风格。这样避免了一个函数里面实例变量和局部变量命名规则混乱,严重影响可读性。

2.1 文件名

    【建议】文件名的命名应反映出其实现了什么类--包括大小写。遵循你所参与项目的约定。

文件的扩展名应该如下:

 

类别的文件名应该包含被扩展的类名,如:GTMNSString+Utils.h 或``GTMNSTextView+Autocomplete.h``。

2.2 Objective-C++

   【建议】源代码文件内,Ojbective-C++ 代码遵循你正在实现的函数/方法的风格。

    为了最小化 Cocoa/Objective-C 与 C++ 之间命名风格的冲突,根据待实现的函数/方法选择编码风格。实现 @implementation 语句块时,使用 Objective-C 的命名规则;如果实现一个 C++ 的类,就使用 C++ 命名规则。

// file: cross_platform_header.h 

 class CrossPlatformAPI {  

public:  

 ...   

int DoSomethingPlatformSpecific();  // impl on each platform  

private:  

 int an_instance_var_; 

};

// file: mac_implementation.mm 

#include "cross_platform_header.h"  

// A typical Objective-C class, using Objective-C naming. 


@interface MyDelegate : NSObject { 

 @private   int instanceVar_;   

CrossPlatformAPI* backEndObject_; 

  • (void)respondToSomething:(id)something; 

@end 

@implementation MyDelegate 

- (void)respondToSomething:(id)something { 


  // bridge from Cocoa through our C++ backend  

instanceVar_ = backEndObject->DoSomethingPlatformSpecific();

 NSString* tempString = [NSString stringWithInt:instanceVar_]; 

 NSLog(@"%@", tempString); 


}

@end 


 // The platform-specific implementation of the C++ class, using 

// C++ naming.

int CrossPlatformAPI::DoSomethingPlatformSpecific() {  

 NSString* temp_string = [NSString stringWithInt:an_instance_var_];

 NSLog(@"%@", temp_string);   

return [temp_string intValue]; 

}


2.3 类名

    【建议】类名(以及类别、协议名)应首字母大写,并以驼峰格式分割单词。

    应用层 的代码,应该尽量避免不必要的前缀。为每个类都添加相同的前缀无助于可读性。当编写的代码期望在不同应用程序间复用时,应使用前缀(如:GTMSendMessage)。

2.4 类别名

    【建议】类别名应该包含它所扩展的类的名字。同时还应该表明该类别的功能。推荐如下方式:” UILabel+FlickerNumber.h”

2.5  Objective-C 方法名

    【建议】方法名应该以小写字母开头,并混合驼峰格式。每个具名参数也应该以小写字母开头。

    方法名应尽量读起来就像句子,这表示你应该选择与方法名连在一起读起来通顺的参数名。(例如,convertPoint:fromRect: 或 replaceCharactersInRange:withString:)。

    访问器方法应该与他们 要获取的 成员变量的名字一样,但不应该以get作为前缀。

以  get 、 set  开头的方法有特殊的意义,不要随意定义。

  1. set 是属性默认的设置方法,如果函数不是为了设置类成员,则不要用  set  开头,可用  setup  替代。
  2. ii. get 和属性方法无关,但在 Cocoa 中,其标准行为是通过引用传值,而不是直接返回结果的。欲获取变量,直接以变量名为名,如: userInfomation ,而不是  getUserInfomation 。

例如:

- (id)getDelegate;  // AVOID 

- (id)delegate;     // GOOD

     这仅限于 Objective-C 的方法名。C++ 的方法与函数的命名规则应该遵从 C++ 风格指南中的规则。


2.6 变量名

【建议】变量名应该以小写字母开头,并使用驼峰格式。类的成员变量应该以下划线作为后缀。

例如:myLocalVariable、myInstanceVariable_。如果不能使用 Objective-C 2.0 的 @property,使用 KVO/KVC 绑定的成员变量可以以一个下划线作为前缀。


2.6.1 普通变量名

    对于静态的属性(int 或指针),不要使用匈牙利命名法。尽量为变量起一个描述性的名字。不要担心浪费列宽,因为让新的代码阅读者立即理解你的代码更重要。例如:

2.6.2 实例变量

    实例变量应该混合大小写,并以下划线作为后缀,如 usernameTextField_。然而,如果不能使用 Objective-C 2.0(操作系统版本的限制),并且使用了 KVO/KVC 绑定成员变量时,我们允许例外。这种情况下,可以以一个下划线作为成员变量名字的前缀,这是苹果所接受的键/值命名惯例。如果可以使用 Objective-C 2.0,@property 以及 @synthesize 提供了遵从这一命名规则的解决方案。

2.6.3 常量

常量名(如宏定义、枚举、静态局部变量等)应该以小写字母 k 开头,使用驼峰格式分隔单词,如:kInvalidHandle,kWritePerm。


2.7 通知命名

【建议】基本命名格式是: [与通知相关的类名] + [Did | Will] + [UniquePartOfName] + Notification 例如:


NSApplicationDidBecomeActiveNotification

NSWindowDidMiniaturizeNotification

NSTextViewDidChangeSelectionNotification

NSColorPanelColorDidChangeNotification

2.8协议名

【建议】常用的 delegate、dateSource 做结尾外,还可以使用 …ing 这种形式,如:NSCoding、NSCopying、NSLocking


3注释

3.1 声明部分的注释

   【建议】每个接口、类别以及协议应辅以注释,以描述它的目的及与整个项目的关系。

// A delegate for NSApplication to handle notifications about app 

// launch and shutdown. Owned by the main app controller.

 @interface MyAppDelegate : NSObject {  

 ...

 }

 @end

如果你已经在文件头部详细描述了接口,可以直接说明 “完整的描述请参见文件头部”,但是一定要有这部分注释。

另外,公共接口的每个方法,都应该有注释来解释它的作用、参数、返回值以及其它影响。

    为类的线程安全性作注释,如果有的话。如果类的实例可以被多个线程访问,记得注释多线程条件下的使用规则。

3.2 实现部分的注释

【建议】使用 | 来引用注释中的变量名及符号名而不是使用引号。

这会避免二义性,尤其是当符号是一个常用词汇,这使用语句读起来很糟糕。例如,对于符号 count :

// Sometimes we need |count| to be less than zero.

或者当引用已经包含引号的符号:

// Remember to call |StringWithoutSpaces("foo bar baz")|

注:不过中文注释的话可以忽略该条

3.3 对象所有权

   【建议】当与 Objective-C 最常规的作法不同时,尽量使指针的所有权模型尽量明确。

     继承自 NSObject 的对象的实例变量指针,通常被假定是强引用关系(retained),某些情况下也可以注释为弱引用(weak)或使用 __weak 生命周期限定符。同样,声明的属性如果没有被类 retained,必须指定是弱引用或赋予 @property 属性。然而,Mac 软件中标记上 IBOutlets 的实例变量,被认为是不会被类 retained 的。

    当实例变量指向 CoreFoundation、C++ 或者其它非 Objective-C 对象时,不论指针是否会被 retained,都需要使用 __strong 和 __weak 类型修饰符明确指明。CoreFoundation 和其它非 Objective-C 对象指针需要显式的内存管理,即便使用了自动引用计数或垃圾回收机制。当不允许使用 __weak 类型修饰符(比如,使用 clang 编译时的 C++ 成员变量),应使用注释替代说明。

强引用及弱引用声明的例子:

@interface MyDelegate : NSObject { 

 @private   IBOutlet NSButton *okButton_;  // normal NSControl; implicitly weak on Mac only   


 AnObjcObject* doohickey_;  // my doohickey  

__weak MyObjcParent *parent_;  // so we can send msgs back (owns me)    // non-NSObject pointers...  


__strong CWackyCPPClass *wacky_;  // some cross-platform object   


__strong CFDictionaryRef *dict_;

 }

 @property(strong, nonatomic) NSString *doohickey;

 @property(weak, nonatomic) NSString *parent;

 @end



3.4 方法声明注释

【建议】声明的方法,要注释方法功能,参数意义,返回值,格式如下:

//含参数,有返回值的方法注释

/**

 *  初始化方法-2

 *  @param frame frame

 *  @param array 标题数组

 *  @param index 默认选中标题索引

 *  @return WJSegmentView

 */

- (instancetype)initWithFrame:(CGRect)frame

                   titleArray:(NSArray *)array

                     sellectIndex:(long)index;

      

//无参,无返回值方法注释

/**移除键盘*/

+ (void)removeKeyBoard{

    NSArray *windows = [[UIApplication sharedApplication] windows];

    for(UIWindow *window in windows){

        [CommonMethods textViewTextFieldResignFirstResponder:window];

    }

}


3.5 属性的注释

     【建议】在声明文件中声明的实例变量在上一行加注释。或者直接在实例变量后边注释,与项目已有风格保持一致。

格式如下:

@property (nonatomic,copy)NSString *loginID;//登录ID

@property (nonatomic,copy)NSString *userName;//用户名

@property (nonatomic,copy)NSString *realName;//真是姓名

   @property (nonatomic,copy)NSString *shareUrl;//二维码分享链接

   @property (nonatomic,copy)NSString *appShareUrl;//社交分享链接

   @property (nonatomic,copy)NSString *headPortraitAddr;//用户头像


/**标题数组*/

@property (nonatomic,retain)NSArray *titleArray;

/**选中按钮底部背景*/

@property (nonatomic,retain)UIView  *backgroundView;

/**默认选中索引*/

  @property (nonatomic,assign)long     selectIndex;  



4 代码组织

4.1 条件语句

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

- (void)someMethod {  

 if (![someOther boolValue]) {   

  return;  

 }   

 //Do something important

 }


  • 【建议】出现超过两层循环的代码,用函数或block替代

// 为了简化示例,没有错误处理,并使用了伪代码


// 糟糕的例子

- (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;

}

4.2 实现文件目录结构

 【建议】 在函数分组和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


- (IBAction)submitData:(id)sender {}


#pragma mark - Public


- (void)publicMethod {}


#pragma mark - Private


- (void)privateMethod {}


#pragma mark - Protocol conformance

#pragma mark - UITextFieldDelegate

#pragma mark - UITableViewDataSource

#pragma mark - UITableViewDelegate


#pragma mark - NSCopying


- (id)copyWithZone:(NSZone *)zone {}


#pragma mark - NSObject


- (NSString *)description {}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值