iOS代码规范与结构

代码规范

1. h文件代码

类功能说明注释。
属性说明注释。
方法说明注释。
类名加前缀,避免命名空间冲突.

2. m文件代码

  1. 属性:
  • 自动存取:所有的属性都使用getter和setter.(静态,全局变量等不需要),在对象内部除了init,dealloc,set/get此类方法使用_property外,其他地方都使用self.property;
  • 属性声明: 声明时,原子属性放到存储属性后; 如果是私有变量放在扩展(Extension)里面声明,遵守协议声明也最好放到扩展里面.
@interface RWTDetailViewController ()

@property (strong, nonatomic) ADBannerView *iAdView;
@property (copy, nonatomic) NSString *name;
@end

补充 1:关于_property和self.property:业内对两种使用方式一直都有争议,唐巧支持使用_property格式, 通过[self setupProperty]这种做法去初始化_property.但是这与苹果的风格不太一致;
补充 2:遵守声明时的属性特质:不管你是重写的setter/getter方法,还是在初始化方法里面进行属性赋值.你依然必须遵守@property声明中的属性特质.例如:

-(instances)initWithName:(NSString*)name {
    if(self = [super init]) {
      _name = [name copy];
    }
    return self;
}

补充3: Category和Extension:创建category类名格式应该是:+Private.h, Category主要用于为现有类添加新方法,在不修改原类的情况下扩展其他功能.目的是对类方法进行归类,便于分模块开发;
Extension其实就是匿名Category,比较特殊,可以用来声明新的私有方法和属性,不过实现还是在原类中.
补充 4: 属性的接口设计:尽量使用不可变对象, 如果允许外界改变,最好单独提供方法来修改; 如某些属性仅可用于对象内部修改,那么可在.h中声明为readonly, 而在.m中的扩展里面,继续声明为readwrite.这样外部不可改,内部还可继续使用setter方法,从而也可以触发KVO.(ps.如果想让代码更易阅读,可以把私有方法的声明写在扩展里面,不过很多人偷懒,都不写声明);

  1. XCODE默认换行是4个空格,推 荐使用2个空格,让代码看起来更紧凑,
    设置方法:xcode->preferences->Text Editing->Indentation->Indent width

  2. 多个回调模块,使用{}括号的对齐比使用 方法名中的:对齐要便于阅读。

// blocks are easily readable
[UIView animateWithDuration:1.0 animations:^{
  // something
} completion:^(BOOL finished) {
  // something
}];
  1. 方法名: 多个参数名前的方法要有描述性的关键字,并且不要包含and字段.
 - (void)setExampleText:(NSString *)text image:(UIImage *)image;
 - (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag;
 - (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;

补充:
细节:1. 如果返回新创建的对象,那么方法名首词应该是返回值类型; 2. 把表示参数类型的名词放在参数前; 3. 如果方法是要在当前对象上执行操作,那么就应该包含动词,若还需要参数,则在动词后加上一个或多个名词;4. 不要随意使用缩略的简称;
私有方法:加前缀,以便于区别面向外界的API,这样若要修改其名称或标签时,只需修改内部相关代码即可,不会担心影响外界.最好加下划线和p,方便识别理解. 注意,最好不要单以_做前缀,因为苹果就喜欢用下划线做私有方法前缀,所以要防止覆写系统方法.

  • 常量: 常量需使用staticconst创建,而#define通常用于定义便利方法. 命名规则:如果是在类内部使用,那么使用k做前缀. 如果是要类外部也使用, 则要使用类名为前缀.
// 在.h文件中
UIKIT_EXTERN NSString * const  RWTAboutViewControllerCompanyName;
//.m文件中中
NSString * const RWTAboutViewControllerCompanyName = @"RayWenderlich.com";
    // 私有常量
static CGFloat const kThumbnailHeight = 50.0f;

使用static const的好处就是,这个常量带有类型信息.而#define不会.

  • 点语法: 点语法应该只被用于获取或者改变属性. 其他方式还是使用[ ].
  //正确  
NSInteger arrayCount = [self.array count];
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;

 //错误
NSInteger arrayCount = self.array.count;
[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;
  • 字面变量: 尽量使用字面变量代替原方法:(注意1.原方法创建Array和Dictionary时末尾要加nil.而使用字面变量则不用.注意2.字面变量创建的集合都是不可变的,需要可变的要进行 mutableCopy)
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone": @"Kate", @"iPad": @"Kamal", @"Mobile Web": @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingStreetNumber = @10018;
  • 控制流:
    8.1 开始:第一个{ 要与if/else/switch/while控制流方法处于同一行,}则需要另起一行。
if (!error) {
      return success;
} // 括号不可省

8.2 枚举:用来表示:状态,选项.推荐使用代码提示中宏定义的NS_ENUM()(ps.如果是C++编译,使用NS_OPTIONS宏替换)枚举模块来创建,它可以提供更严谨的类型检测和代码补完功能.

typedef NS_ENUM(NSInteger, RWTLeftMenuTopItemType) {
      RWTLeftMenuTopItemMain      = 1 << 0,
      RWTLeftMenuTopItemShows     = 1 << 1,
      RWTLeftMenuTopItemSchedule  = 1 << 2
};

枚举后面的= 1 << 0是利用二进制位运算,可以方便的判断是否满足枚举的多个组合选项. 例: RWTLeftMenuTopItemMain | RWTLeftMenuTopItemShows就是表示同时满足1和2;

8.3 switch:每一个case分支下执行语句,如果只有一行,不加{}; 如果多行, 使用{};

switch (condition) {
  case 1:
      // ...
      break;
  case 2: {
      // ...
      // Multi-line example using braces
      break;
   }
case 3:
      // ...
      break;
}

default是非必须,要习惯不写,这是由于如果switch的条件是枚举,那么如果以后此枚举选项新增,而不使用default时,编译器会给我们提醒,方便处理.

8.4 三目运算符: 只有在确定能够促进代码整洁与清晰的前提下才使用.非BOOL类型变量必须要与其类型变量做出判断后才能使用。

NSInteger value = 5;
result = (value != 0) ? x : y;
BOOL isHorizontal = YES;
result = isHorizontal ? x : y;
  • 布尔BOOL:声明:@property (assign, getter=isEditable) BOOL editable;,另外不要把对象直接和YES进行比较,
// 一般对象 是否为nil
if (!someObject) {}
// NSString是否有值
if(string.length) {}
// 集合内部value是否为nil
if (dict[key] == [NSNull null]) {}  
  • 注释:

    • 注释及时更新,或者直接删除;
    • 尽量做到代码如注释。
  • 代码换行 * 代码太长需要换行时,与首行要有两个空格间隔.设置: xcode->preferences->Text Editing->Indentation->Line Wrapping 手动设置为2

self.productsRequest = [[SKProductsRequest alloc] 
    initWithProductIdentifiers:productIdentifiers];
  • if扩展: 减少if嵌套,扁平化多个返回条件.
- (void)someMethod {
  if (![someOther boolValue]) {
    return;
  }
  //Do something important
}

// 错误处理时, if条件必须是error本身.
NSError *error;
if (![self trySomethingWithError:&error]) {
  // Handle Error
}
// 不好的写法:
NSError *error;
[self trySomethingWithError:&error];
if (error) {
  // Handle Error
}

ps. 苹果新语言Swift的if guard语句就是为了解决if嵌套问题.

3.控制器结构:

#pragma mark - DataRequest
#pragma mark - DefaultSetting/Intial
#pragma mark - View生命周期
#pragma mark -Target/Event response
#pragma mark -  Private (功能性方法一般放到category中,ViewController基本上是大部分业务的载体,尽量不放与业务无关的代码)
#pragma mark - Deletate/DataSource
#pragma mark - Setter/Getter
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {...}
#pragma mark - NSObject/Override
- (NSString *)description {...}

如有需要,可以继续在其中划分子代码区.

-(void)loadView {
    // 第一次加载控制器时会调用.
    // 只有需要使用自定义view才重写,且不需要调用[super loadView].
    self.view = self.customView;
}
- (void)viewDidLoad
{
    // 此方法中只addSubview,属性初始化都放到Getter去做, 尽量简洁。
    self.view.backgroundColor = [UIColor whiteColor];
    
    [self.view addSubview:self.firstTableView];
    [self.view addSubview:self.secondTableView];
    
    // 如果是使用autolayout设置constraints.则在这里自定义一个方法调用.例:
    [self layoutSubviewConstraints];
}
- (void)layoutSubviewConstraints
{
    [self.view addConstraints:xxxConstraints];
    [self.view addConstraints:yyyConstraints];
    [self.view addConstraints:zzzConstraints];
}

- (void)viewWillAppear
{
    //在此方法只中布局子控件的frame,
    //如果是autolayout就在viewdidload add完subview之后开个方法去单独写constraints。
}

- (void)viewDidAppear
{
    //此方法中添加监听/Notification等
}

#pragma mark - getters and setters
- (UIView *)label {
    if (_label == nil) {
        _label = [[UILabel alloc] init];
        _label.text = @"1234";
        _label.font = [UIFont systemFontOfSize:12];
        ... ...
        //label的子view,还有gestureRecognizer等也是可以放到getter中的,getter相当于是它的工厂方法.
    }
    return _label;
}

4. 自定义View

  1. 重写initwithFrame方法,此方法中创建-添加-记录子控件,并且可以设置不需要数据的和非frame的格式属性.
  2. 有些行为需要通知外部,对象间通信的需求等 使用代理或通知或block.
- (instancetype)initWithFrame:(CGRect)frame {
  if (self = [super initWithFrame:frame]) {
    [self addOwnViews];
    [self configOwnViews];
  }
  return self;
}

- (void)addOwnViews {
  // 添加子控件
}

- (void)configOwnViews {
  // 配置子控件
}
//在外界设置好self的frame后调用,来设置子控件frame.
- (void)relayoutFrameOfSubViews {
  // 设置frame,
}

// 模型数据set方法
- (void)setModel(Model*)model 
{
    _model = model;
    ...
    // 设置动态属性    
}


// 补充: 重新布局子控件frame.
- (void)layoutSubviews  
{ 
    [super layoutSubviews]
//1. 每次addSubview和frame变动就会调用.
//2. ScrollView滚动触发. 
//3. Screen旋转
//4. 手动调用 setNeedsLayout.(异步,非即时的)  layoutIfNeeded:如果有需要刷新的标记,立即刷新.
  PS.所以若有立即刷新, 要同时调用这两个方法, (视图第一次显示时默认被标记,此时只需调用ifNeed.)
所以: 如果你想强制更新布局,你可以调用setNeedsLayout方法;如果你想立即显示你的views,你需要调用layoutIfNeeded方法。
}

类似的drawRect和setNeedsDisplay方法; drawRect是在视图被真正显示的时候,如果layer的contents没有内容,就会被调用.
即: layoutSubviews是更新自己子视图们的frame;而drawRect是创建自己的视图内容;

5. 补充:

1. 类簇模式.

类簇模式可以灵活应对多个类,将他们的实现细节隐藏在同一个抽象基类后面,保持界面整洁,系统. 例如 + (UIButton *)buttonWithType:(UIButtonType) type方法会根据传入的类型,来返回不同的button;
类簇底层实现很简单,就是枚举+switch,根据type实现不同的样式;

类簇注意的是:
1.要创建的子类应该继承自抽象类.
2.子类应该定义自己的数据存储方式.
3.子类应该覆写超类文档中指明的覆写方法,例如,要编写系统NSArray的子类,就要实现count及"objectAtIndex:"方法.

2. 使用自动释放池来降低内存峰值

及时释放大量创建的临时对象,避免内存过高;

NSArray *databaseRecodrs;
NSMutableArray *people = [NSMutableArray new];
for (NSDictionary *record in databaseRecords) {
  @autoreleasepool {
    EocPerson *person = [[EOCPerson alloc] initWithRecordLrecord];
[people addObject:person];
  }
}

3. 使用block回调代替委托回调.使代码更紧凑.

#import 

@class EOCNetworkFetcher;
typedef void (^EOCNetworkFetcherCompletionHandler) (NSData *data, NSError *error);

#interface EOCNetworkFetcher : NSObject
- (instance)initWithURL:(NSURL*)url;
- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completionHandler;


调用API的时候:
EOCNetworkFetcher *fetcher = [EOCNetworkFetcher alloc] initWithURL:url];
[fetcher startWithCompletionHandler:^(NSData *data, NSError *error) {
    if (error) {
      //handle failure
    } else {
      // handle success
    }
}];

你可能感兴趣的:(iOS代码规范与结构)