Objective-C Coding Guide

Objective-C Coding Guide


版本: 1.1

修订: iOS茄子快传组

时间: 2017/08/28


总则: 英文单词拼写一定要正确、交流发音一定要准确、动名词使用一定要恰当


命名

  • 类方法名、对象方法名、成员属性名、临时变量名 要遵循 驼峰 规则。首字母小写,其余单词首字母大写

  • 类文件名单词首字母全部大写,注意添加项目前缀。

  • 类方法名、对象方法名、成员属性名、临时变量名单词不可简写,要写的有意义。

正确:
- (void)resetBarAttribute:(UIColor *)normal highlight:(UIColor *)highlight font:(UIFont *)font
{
    for (UIButton *bar in self.bars) {
        [self setBar:bar :normal :highlight :font];
    }
}

不建议:
- (void)resetBarAttribute:(UIColor *)normal :(UIColor *)highlight :(UIFont *)font
{
    for (UIButton *bar in self.bars) {
        [self setBar:bar :normal :highlight :font];
    }
}

  • 为了避免混淆造成不必要的问题,自己声明的临时变量或者全局静态变量,不应该以 _ 下划线开头,因为系统为成员属性生成的成员变量是以 _ 下划线开头。更不应该用系统自有的变量或方法来声明一个变量 比如 *new

注释

  • 注释应在代码的上方或者右方,不可在下方

项目结构

  • 工具类的命名要为 xxxTool 或者 xxxUtil,不建议 xxxHelper

  • 类文件尽量放在指定的文件夹中,文件夹的名称不建议使用复数,文件夹名称不建议加项目前缀

  • 为了避免文件杂乱,物理文件应该保持和 Xcode 项目文件同步。Xcode 创建的任何组(group)都必须在文件系统有相应的物理映射。为了更清晰,代码可按照类型进行分组,也可按功能进行分组。


代码格式化

  • 空格的添加

    • +-*/?:

    • 类方法、对象方法的调用

    正确:
    [[NSObject alloc] init];
    
    不建议:
    [[NSObject alloc]init];
    
    
    • 类方法、对象方法的声明和调用
    声明:
    - (void)run;
    
    + (void)personWithAge:(NSUInteger)age;
    
    调用:
    - (void)run {
    
    }
    
    + (void)personWithAge:(NSUInteger)age {
    
    }
    
    

    (* 注意:类方法对象方法在.m中实现的时候不允许复制,要用 +- 直接提示出方法来写,以免出现方法实现末尾出现 ; 分号的情况 *, 如下所示:)

    + (void)personWithAge:(NSUInteger)age; {
    
    }
    
    
  • ifforwhiletrycatch 等语句自占一行,执行语句不得紧跟其后,并且条件和这些关键词之后都有空格。_ (建议:不论执行语句有多少都要加 “{ }”) _ 。这样可以防止书写和修改代码时出现失误。

  • 宏要全部大写,并且要用必要的下划线分隔开单词;常量要用字母k开头后面单词首字母全部大写

#define MAX_SIZE 30

static const NSUInteger kUsernameRow = 0;

  • 一个方法应该太多行,暂定不要超过 100 行。如果其中的过程没有提取为独立方法的必要,则不必限制长度

  • 代码行最大长度宜控制在 80 个字符以内。代码行不要过长,否则眼睛看不过来,不过此规则可以视情况适当放宽。

不要出现下面这样:

/**用于获取每个section应当返回的cell个数*/
-(NSInteger)getSectionCount:(NSDictionary*)dict
{
    return (dict[kPhotos]==nil?0:[dict[kPhotos] count]+1)+(dict[kMusics]==nil?0:[dict[kMusics] count]+1)+(dict[kFiles]==nil?0:[dict[kFiles] count]+1)+(dict[kVideos]==nil?0:[dict[kVideos] count]+1)+(dict[kContacts]==nil?0:[dict[kContacts] count]+1)+(dict[kFloders]==nil?0:[dict[kFloders] count]+1);
}

  • 空行的使用,要内函数内模块来分。适当的添加空行,可以是代码结构更为清晰,减少错误。

代码组织

  • 可变数组、可变字典等可变的类型的命名,一定要以 M 结尾
NSMutableArray *mediaItemsArrM = [NSMutableArray array];

字符串后     xxxStr/xxxStrM

集合后     xxxArray/xxxArrayM  xxxArr/xxxArrM

字典后     xxxDict/xxxDictM

  • 可变数组和可变字典只要元素类型确定,一定要写上 泛型 指定类型。如果元素类型是多态可变的可不写
NSMutableArray *mediaItemsArrM = [NSMutableArray array];

  • 可变数组、可变字典等可变的类型不可用于返回值和参数,一定要做出及时的转换后再使用

  • 类方法和对方法声明的时候,一定要指定参数或返回值是否可 nil,做到及时断言处理

- (nonnull NSArray *> *)selectMediaItemsArr;

- (void)saveWithMediaItemsArr:(nonnull NSArray *)mediaItemsArr;


  • 不可使用魔鬼数字和魔鬼字母

  • 对象在使用前注意判 nil,特别是将对象添加到可变数组和可变字典的时候。还有一些方法参数没有明确说不可为 nil,但依然会崩溃,比如:

[NSURL fileURLWithPath:nil];

[[NSFileManager defaultManager] createSymbolicLinkAtURL:nil withDestinationURL:nil error:nil];

[NSJSONSerialization dataWithJSONObject:nil options:0 error:nil];


  • 修饰符的位置:为便于理解,应当将修饰符 *& 紧靠变量或参数
@property (nonatomic, copy, readonly, nonnull) NSString *layerId;

CFStringRef FileBlockMD5HashCreateWithPath(NSString *filePath)

UpdateHash(&hashObject, fileHandle, startIndex, blockSize);

[[NSFileManager defaultManager] contentsOfDirectoryAtPath:[self accessToTheMainFilePath] error:&error];


其他

  • 枚举类型的定义

    • NS_ENUM 枚举项的值为 NSIntegerNS_OPTIONS 枚举项的值为 NSUInteger。像下面这样:
    typedef NS_ENUM(NSInteger, IJKMPMoviePlaybackState) {
        IJKMPMoviePlaybackStateStopped = 0,
        IJKMPMoviePlaybackStatePlaying,
        IJKMPMoviePlaybackStatePaused,
        IJKMPMoviePlaybackStateInterrupted,
        IJKMPMoviePlaybackStateSeekingForward,
        IJKMPMoviePlaybackStateSeekingBackward
    };
    
    
    typedef NS_OPTIONS(NSUInteger, UISwipeGestureRecognizerDirection) {
      UISwipeGestureRecognizerDirectionNone = 0,  //值为0
      UISwipeGestureRecognizerDirectionRight = 1 << 0,  //值为2的0次方
      UISwipeGestureRecognizerDirectionLeft = 1 << 1,  //值为2的1次方
      UISwipeGestureRecognizerDirectionUp = 1 << 2,  //值为2的2次方
      UISwipeGestureRecognizerDirectionDown = 1 << 3  //值为2的3次方};
    
    
    • NS_ENUM定义 通用枚举,NS_OPTIONS 定义位移枚举
    位移枚举即是在你需要的地方可以同时存在多个枚举值如这样:
    UISwipeGestureRecognizer *swipeGR = [[UISwipeGestureRecognizer alloc] init];
    swipeGR.direction = UISwipeGestureRecognizerDirectionDown | UISwipeGestureRecognizerDirectionLeft | UISwipeGestureRecognizerDirectionRight;
    //这里几个枚举项同时存在表示它的方向同时包含1.向下2.向左3.向右
    
    而NS_ENUM定义的枚举不能几个枚举项同时存在,只能选择其中一项,像这样:
    NSMutableParagraphStyle *paragraph = [[NSMutableParagraphStyle alloc] init];
    paragraph.baseWritingDirection = NSWritingDirectionNatural;
    
    
  • 类方法新建对象
ASAdWrapper 类

+ (nonnull instancetype)wrapperWithLayerId:(nonnull NSString *)layerId fullAdId:(nonnull NSString *)fullAdId expiredDuration:(long long)expiredDuration;

ASAdInfo 类

+ (nonnull instancetype)infoWithLayerId:(nonnull NSString *)layerId fullAdId:(nullable NSString *)fullAdId;


  • 对象方法新建对象
ASAdWrapper 类

- (instancetype)initWithLayerId:(NSString *)layerId fullAdId:(NSString *)fullAdId expiredDuration:(long long)expiredDuration AdObject:(NSObject *)adObj adKeyword:(NSString *)adKeyword;

ASAdInfo 类

+ (instancetype)initWithLayerId:(NSString *)layerId fullAdId:(NSString *)fullAdId;

  • 代理方法的命名

    • 用delegate做后缀

    • 用optional修饰可以不实现的方法,用required修饰必须实现的方法

    • 当你的委托的方法过多, 可以拆分数据部分和其他逻辑部分, 数据部分用dataSource做后缀

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

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

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

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

@protocol UITableViewDataSource

@required

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

@optional

//回调方法的参数只有类自己
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; // Default is 1 if not implemented

@end

@protocol UITableViewDelegate

@optional

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

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

@end

  • .h 文件的组织:系统头文件放在前面,自定义的头文件放在后面,接着是 @class, 中间空行; 成员属性写在上方,方法写在下方
#import 

#import "MemberVariable.h"

@class ASUserInfo;

@interface DateilViewController : UIViewController
{
    //变量
    NSString *_dataString;
}


//属性
@property (nonatomic, strong) NSDictionary *supplyDateilDict;
@property (nonatomic, strong) NSMutableArray *supplyArray;

//方法
- (void)handleData;
- (void)createView;


@end

  • .h 文件中引用要使用 @class,在.m引用要使用 #import "xxx.h",加快编译速度
#import 
#import 
@class ASAdWrapper;
@class ASAdInfo;

@interface ASAdCache : NSObject

SingletonH(Cache);

- (void)pushToAdCacheWithAdWrappersArray:(nonnull NSArray *)adWrappersArr;

- (nullable NSArray *)popFromAdCacheWithAdInfo:(nonnull ASAdInfo *)adInfo;

- (nullable NSArray *)popFromAdCacheWithAdInfo:(nonnull ASAdInfo *)adInfo supportUseMinCount:(BOOL)supportUseMinCount;

- (BOOL)hasAdCacheWithAdInfo:(nonnull ASAdInfo *)adInfo;

@end

  • 新建一个对象不建议使用 new,太过暴力,要使用对应的类方法和对象方法来新建对象。尽管很多时候能用 new 代替 alloc init 方法,但这可能会导致调试内存时出现不可预料的问题。Cocoa的规范就是使用 alloc init 方法,使用 new 会让一些读者困惑。

  • 类方法、对象方法返回对象自身要使用 instancetype 而不是使用 id。这样IDE会帮你检查类型,及时显示警告,减少错误。

  • 属性特性:weakstrongatomicnonatomicreadonly
    gettersetternonnullnullable

@property (nonatomic, copy, readonly, nonnull) NSString *layerId; 

@property (nonatomic, copy, readonly, nullable) NSString *fullAdId;

@property (nonatomic, copy, readonly, nonnull) NSString *style; 

@property (nonatomic, copy, readonly, nullable) NSString *placementId; 

@property (nonatomic, strong, nonnull) NSMutableSet *excludeKeywordsSetM;

@property (nonatomic, assign, readonly) NSUInteger adPullCount; 

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

  • 使用//TODO:说明 标记一些未完成的或完成的不尽如人意的地方
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    //TODO:增加初始化
    return YES;
}

  • 函数

    • 一个函数只做一件事(单一原则),每个函数的职责都应该划分的很明确(就像类一样)

    • 对于有返回值的函数(方法),每一个分支都必须有返回值

    • 对输入参数的正确性和有效性进行检查,参数错误立即返回

    • 如果在不同的函数内部有相同的功能,应该把相同的功能抽取出来单独作为另一个函数

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

  • Category 扩展不可覆盖原有类的方法

  • 注意 block 循环强引用

  • 尽量不要用 KVO, 要用 NSNotification 或者 delegate 代替

你可能感兴趣的:(Objective-C Coding Guide)