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<ASMediaItem *> *mediaItemsArrM = [NSMutableArray array];

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

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

- (nonnull NSArray<NSArray<ASMediaItem *> *> *)selectMediaItemsArr;

- (void)saveWithMediaItemsArr:(nonnull NSArray<ASMediaItem *> *)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 <UIKit/UIKit.h>

#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 <Foundation/Foundation.h>
#import <uShareitSDK/AndyDictStore.h>
@class ASAdWrapper;
@class ASAdInfo;

@interface ASAdCache : NSObject

SingletonH(Cache);

- (void)pushToAdCacheWithAdWrappersArray:(nonnull NSArray<ASAdWrapper *> *)adWrappersArr;

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

- (nullable NSArray<ASAdWrapper *> *)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<NSString *> *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 代替

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容