IOS风格指南

yu.zhai
2022-12-04 / 0 评论 / 127 阅读 / 正在检测是否收录...

iOS Coding Style Guide

命名规范

我们尽可能遵守 Apple 的命名约定, 其推荐使用长的,描述性强的方法和变量名,使其阅读起来更加清晰易懂。不能随意使用缩写,导致其他人员阅读代码困难。Coding Guidelines for Cocoa

  1. 前缀

    项目名称、类名、文件名都应该保持一致的前缀名,NAP iOS项目使用YN作为前缀。

  2. 类名

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

    @interface YNHomeViewController : YNBaseViewController
    
    @end
  3. property变量

    小驼峰式命名:

    • 第一个单词以小写字母开始,后面的单词的首字母全部大写。
    • 属性的关键字推荐按照 原子性,内存管理,读写的顺序排列。
    • Block、NSString属性应该使用copy关键字。
    • 代理使用weak关键字防止循环引用。
    @property (nonatomic, strong, readonly) UILabel *subjectLabel;
    @property (nonatomic, copy) NSString *statusName;
    @property (nonatomic, weak) id <YNLogisticsProductsCellDelegate>delegate;
    @property (nonatomic, copy) void(^actionBlock)(__nullable id);
  4. 枚举

    • Enum中枚举内容的命名需要以该Enum类型名称开头。
    • NS_ENUM定义通用枚举,NS_OPTIONS定义位移枚举。
    typedef NS_OPTIONS(NSUInteger, YNLogLevel){
        YNLogLevelError      = 1 << 0,
        YNLogLevelWarning    = 1 << 1,
        YNLogLevelInfo       = 1 << 2,
        YNLogLevelDebug      = 1 << 3,
        YNLogLevelVerbose    = 1 << 4
    };
    
    typedef NS_ENUM(NSUInteger, YNContentAlignment) {
        YNContentAlignmentLeft      = 0,    
        YNContentAlignmentCenter    = 1,    
        YNContentAlignmentRight     = 2
    };
  5. 宏和常量

    • 对于宏定义的常量

    define 预处理定义的常量全部大写,单词间用 _ 分隔,宏定义中如果包含表达式或变量,表达式或变量必须用小括号括起来。

    • 对于类型常量

    对于局限于某编译单元(实现文件)的常量,以字符k开头,例如kAnimationDuration,且需要以static const修饰。

    对于定义于类头文件的常量,外部可见,则以定义该常量所在类的类名开头,例如EOCViewClassAnimationDuration, 仿照苹果风格,在头文件中进行extern声明,在实现文件中定义其值。

    //宏定义的常量
    #define ANIMATION_DURATION    0.3 
    #define MY_MIN(A, B)  ((A)>(B)?(B):(A)) 
     
    
    //局部类型常量 
    static const NSTimeInterval kAnimationDuration = 0.3; 
     
      
    //外部可见类型常量 
    //YNMagazinePresenter.h 
    extern NSString * const YNMagazineChannelTypeFemaleNewest;
     
    //YNMagazinePresenter.m 
    NSString * const YNMagazineChannelTypeFemaleNewest = @"1";
  6. 方法

    • 方法名用小驼峰式命名。
    • 方法名不要使用new作为前缀。
    • 不要使用and来连接属性参数,如果方法描述两种独立的行为,使用and来串接它们。
    • 方法实现时,如果参数过长,则令每个参数占用一行,以冒号对齐。
    • 一般方法不使用前缀命名,私有方法可以使用统一的前缀来分组和辨识。
    • 方法名要与对应的参数名保持高度一致。
    //不要使用 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; 
     
    //不要加get  
    - (NSSize)cellSize;  //推荐  
    - (NSSize)getCellSize;  //反对   
     
  7. 方法参数

    • 不要在参数名中使用 pointer 或 ptr,让参数的类型来说明它是指针
    • 避免使用 one, two,...,作为参数名,更不要用1,2,...
    • 避免为节省几个字符而缩写

    采用以下参数标签与参数组合的惯例

    ...action:(SEL)aSelector
    ...alignment:(int)mode
    ...atIndex:(int)index
    ...content:(NSRect)aRect
    ...doubleValue:(double)aDouble
    ...floatValue:(float)aFloat
    ...font:(NSFont *)fontObj
    ...frame:(NSRect)frameRect
    ...intValue:(int)anInt
    ...keyEquivalent:(NSString *0charCode
    ...length:(int)numBytes
    ...point:(NSPoint)aPoint
    ...stringValue:(NSString *)aString
    ...tag:(int)anInt
    ...target:(id)anObject
    ...title:(NSString *)aString

代码注释规范

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

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

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

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

  1. 属性注释

    写在属性上方,三斜线开头斜线后面跟一个空格后面跟注释内容;可使用Xcode自带模板快捷添加(默认快捷键:⌥+⌘+/)。

    /// 文章ID,用于下面的更多精彩内容去重
    @property (nonatomic, copy) NSArray <NSString *> *articleIds;
  2. 方法声明注释

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

    三斜线开头,斜线后跟一个空格;第一行为方法描述,第二行开始为方法参数以@param标记(默认快捷键:⌥+⌘+/)。

    /// 快速生成渐变图层
    /// @param frame 图层位置大小
    /// @param beginColor 起始颜色
    /// @param endColor 结束颜色
    + (CAGradientLayer *)gradientLayerWithFrame:(CGRect )frame
                                     beginColor:(UIColor *)beginColor
                                       endColor:(UIColor *) endColor;
  3. TODO

    使用// TODO: 说明 标记一些未完成的或完成的需要优化的地方。

    // TODO: restore scrollView state
    // This is temporary solution. Have to implement the save and restore scrollView state
    UIScrollView *superscrollView = strongLastScrollView;
  4. FIXME

    使用// FIXME: 说明 标记一些有bug的或以临时方案解决的地方。(使用// FIXME: 标记在检索器会出现提醒图)

    WeChat393050b24040fb44edc9faba00ac10df.png

    // FIXME: 等待接口字段
    // FIXME: someday check the return codes on these binds.

代码格式规范

  1. 指针*位置

    跟在类型后面用空格隔开紧跟着变量名。

    NSURL *url;
    
    - (id)initWithChildren:(NSArray *)children;
  2. 方法声明和实现

    +、-后面跟一个空格,方法名和第一个参数之间不留空格;方法体的第一个大括号跟在方法名末尾用空格隔开。

    // 方法声明
    - (id)initWithChildren:(NSArray *)children;
    
    + (instancetype)actionWithTitle:(NSString *)title style:(UIPreviewActionStyle)style handler:(void (^)(UIPreviewAction *action, UIViewController *previewViewController))handler;
    
    // 方法实现
    - (id)initWithChildren:(NSArray *)children {
        self = [super initWithChildren:children];
        return self;
    }
    
    + (instancetype)actionWithTitle:(NSString *)title style:(UIPreviewActionStyle)style handler:(void (^)(UIPreviewAction *action, UIViewController *previewViewController))handler {
        UIPreviewAction *action = [[UIPreviewAction alloc] init];
        action.title = title;
        ...
        return action;
    }
  3. 对implementation方法进行分组

    #pragma mark - Life cycle
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    }
    
    #pragma mark - Private methods
    
    #pragma mark - Delegates
    
    #pragma mark - Event response
    
    #pragma mark - Setter
    
    #pragma mark - Getter

编码规范

  1. if语句

    • 尽量列出所有分支(穷举所有的情况),而且每个分支都须给出明确的结果。
    • 不要使用过多的分支,要善于使用return来提前返回错误的情况,把最正确的情况放到最后返回。
    • 条件过多,过长的时候应该换行;条件表达式如果很长,则需要将他们提取出来赋给一个BOOL值,或者抽取出一个方法。
    NSString *hintStr; 
     
    if (count < 3) { 
      hintStr = @"Good"; 
    } else { 
      hintStr = @""; 
    }
    if (!user.userName) return NO; 
     
    if (!user.password) return NO; 
     
    if (!user.email) return NO;   
     
    return YES;
    if (condition1 && 
     
        condition2 && 
     
        condition3) { 
      // 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; 
    }
  2. for循环

    避免在for循环内修改循环变量,防止for循环失去控制。如果必须修改,可以创建一个临时变量进行遍历或者使用NSArray的enumerateObjectsUsingBlock:方法进行遍历。

    NSArray *arr = [NSArray arrayWithArray:mArr];
    for (NSMutableDictionary *dic in arr) {
        if ([dic[@"a"] isEqualToString:@"3"]) {
            [mArr removeObject:dic];
        }
    }
    
    [mArr enumerateObjectsUsingBlock:^( id obj, NSUInteger idx, BOOL *stop) {
        if ([[obj objectForKey: @"a"] isEqualToString:@"3"]) {
            *stop = YES ;
            [mArr removeObject:obj];
        }
    }];
  3. 方法

    • 单一性原则,每个函数的职责都应该划分的很明确(就像类一样)。
    • 对于有返回值的方法,每一个分支都必须有返回值。
    • 对输入参数的正确性和有效性进行检查。
    • 如果在不同的方法内部有相同的功能,应该把相同的功能抽取出来单独作为另一个方法。
    • 将方法内部比较复杂的逻辑提取出来作为单独的方法。
    // 推荐写法
    - (void)viewDidLoad {
        [super vidwDidLoad];
        
        [self setupUI];
        [self prepareData];
    }
    
    - (void)setupUI {
        
    }
    
    - (void)prepareData {
        
    }
    
    // 不推荐
    - (void)viewDidLoad {
        [super vidwDidLoad];
        
        [self build];
    }
    
    - (void)build {
        // setupUI
        ...
        
        // prepareData
        ...
    }
    // 推荐写法
    - (int)function { 
        if(condition1){ 
            return count1 
        }else if(condition2){ 
            return count2 
        }else{ 
           return defaultCount 
        } 
    }
    
    // 不推荐
    - (int)function { 
        if(condition1){ 
            return count1 
        }else if(condition2){ 
            return count2 
        } 
    }
    - (void)functionWithParam:(id)param { 
          if([param isUnavailable]){ 
              return; 
          }  
         //Do some right thing 
    }
  4. 属性和变量

    • 数据成员保持最小公开原则
    • 在不需要改变一个属性时,添加readonly
    • 最好使用auto-synthesis。
    • 当使用属性,实例变量时应该使用self.来访问,这意味着所有的属性将很容易区分,因为它们都使用 self. 开头
  5. nil / Nil / NULL / NSNull

    Objective-C中默认所有的指针指向nil,nil最显著的行为是可以有消息发送给它,结果返回0

    if (name != nil && [name isEqualToString:@"Steve"]) { ... }
    // …可以被简化为:
    if ([name isEqualToString:@"steve"]) { ... }
    SymbolValueMeaning
    NULL(void *)0literal null value for C pointers
    nil(id)0literal null value for Objective-C objects
    Nil(Class)0literal null value for Objective-C classes
    NSNull[NSNull null]singleton object used to represent null
  6. Booleans

    Objective-C用BOOL来编码真值。它是signed char的typedef,并且用宏YES和NO来相应的表示真和假;在Objective-C中,当遇到处理真值的参数,属性和实例变量时,使用类型BOOL。当分配字面值时,使用宏YES和NO;如果BOOL属性的名称表示为一个形容词,该属性可以省略“is”字头,但需要为get方法按照常规指定名称。

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

    判断真假时不要与字面值比较

    // 推荐
    if (someObject) {
    }
    if (![anotherObject boolValue]) {
    }
    // 不推荐
    if (someObject == nil) {}
    if ([anotherObject boolValue] == NO) {}
    if (isAwesome == YES) {} // Never do this.
    if (isAwesome == true) {} // Never do this.
    NameTypedefHeaderTrue ValueFalse Value
    BOOLsigned charobjc.hYESNO
    bool_Bool (int)stdbool.htruefalse
    booleanunsigned charMacTypes.hTRUEFALSE
    NSNumber__NSCFBooleanFoundation.h@(YES)@(NO)
    CFBooleanRefstructCoreFoundation.hkCFBooleanTruekCFBooleanFalse

团队约定

  1. 字典类型参数使用YNParameters进行包装,可以避免空数据带来的crash。

    YNParameters *parameter = [YNParameters new];
    [parameter addParameter:@"order_id" value:order_id];
    [parameter addParameter:@"current_page" value:current_page];
    [parameter addParameter:@"order_type" value:orderType];
    [YNSensorsAnalytic trackEvent:YNSensorsAnalytic_OrderPageOrderDelete properties:parameter.parameterDict];
  2. 数组取值使用YYKit的objectOrNilAtIndex:方法,避免数组越界。

    __kindof UIViewController *viewController = [self.viewControllers objectOrNilAtIndex:index];
  3. 字符串文案使用yn_safeString:方法进行转换,避免显示异常。

    NSString *title = [NSString yn_safeString:model.title];
  4. 使用枚举定义有限类型而非简单数字判断。

    typedef NS_ENUM(NSUInteger, YNContentAlignment) {
        YNContentAlignmentLeft      = 0,   
        YNContentAlignmentCenter    = 1,    
        YNContentAlignmentRight     = 2,
    };
    
    if (aligment == YNContentAlignmentCenter) {
        ...
    } else {
      ...
    }
    switch (aligment) {
    case YNContentAlignmentCenter:{
    ...
    break;
    }
    case YNContentAlignmentRight:{
      ...
    break;
    }
    default: {
      ...
    break;
    }
    }
    
    // 不推荐
    if (aligment == 2) {
      ...
    } else {
      ...
    }
    
    switch (aligment) {
    case 1:{
      ...
    break;
    }
    case 2:{
      ...
    break;
    }
    default: {
      ...
    break;
    }
    }
  5. 所有测试代码使用DEBUG宏包起来,不将测试代码上传到远程仓库。

    #if DEBUG
    - (void)mock {
        self.title = @"PDP";
        self.brand = @"NET-A-PORTER";
    }
    #endif
  6. 所有接口地址统一在YNNetInterface进行维护。
0

评论

博主关闭了所有页面的评论