Objective-C编码规范

首选Swift,除非部分需要使用OC的场景:

  • 基础组件(需要兼容OC调用)
  • 需要和C/C++交互

文件命名

  • 文件使用PG前缀, 注意大写.
  • 业务组件需要添加模块前缀, 比如Order/HomePage.

PGOrderViewController.h / PGHomePageViewController.m

提示: 避免潜在的命名冲突.

分类

  • 分类使用类名+扩展字段
@Interface NSObject(Test)

@property (nonatomic) NSInteger pg_num;

- (void)pg_test;

@end

UIView+Addition.h / UIView+Addition.m

常见问题

关于文件命名缩写

比如Button使用Btn, ViewController使用VC.
个人建议不要使用缩写, 缩写会增加理解的难度.除非是一些大家都知道的缩写方式, 比如HTTP/DNS.

属性

@property (nullable, nonatomic, readonly, strong) NSObject *object;
@property (nonatomic, readonly, assign) NSInteger num;
@property (nonatomic, readonly) NSInteger num2;
  • 添加必要的空格, 属性修饰符保持固定的顺序. 保持统一的风格提高可读性

提示: 可以考虑不添加assign, 基础类型默认assign. 参考Apple官方API

使用_替代self读写属性值

self.num = 1;
_num = 1;
  • 性能更好, 不需要调用set/get方法

提示: 除非你需要使用set/get方法.

多使用readonly属性

  • 外部不可修改只读, 更安全

提示: 当你公开读权限的时候, 需要假定这个值会被修改, 而不是你知道这个值没有人会去修改.

不推荐使用实例变量

@interface ViewController () {
    NSString *name;
}
@end
  • 无法添加属性相关修饰.

提示: 如果不想生成get/set方法, 可以添加@dynamic标示.


### `常见问题`
##### `init`方法内使用`self`,而不是`_`
  • (instancetype)init {
    if (self) {
    self.num = 1; // 错误
    _num = 1;
    }
    return self;
    }
##### `copy`/`strong`错误使用
```objc
@property (nonatomic, readonly, strong) NSArray *array; // error
@property (nonatomic, readonly, strong) NSString *array; // error
@property (nonatomic, readonly, copy) NSMutableArray *array; // error

- (NSArray *)elements {
    // return [self.mutableElements copy];
    return self.mutableElements; // error
}
外部不可修改的属性不添加readonly

提示: 应该尽可能减少对外公开的权限, 减少潜在的bug

没有实现set/get方法, 使用self而不是_

方法

分类方法添加前缀

- (void)pg_method {
    
}
- (NSString *)method:(NSString *)string {
    return @"";
}
  • 良好的方法/参数命名
  • 添加适当的空格增加可读性

推荐使用.语法代替[]获取类属性

推荐
NSUserDefaults.standardUserDefaults
mutableElements.copy
不推荐
[NSUserDefaults standardUserDefaults]
[mutableElements copy]
  • 减少嵌套, 代码更简洁

使用instancetype代替id

常见问题

使用id而不是instancetype











使用extern修饰对外开放的常量

//Object.h
extern NSString *const String;
//Object.m
NSString *const String = @"Finish Download";

字面值

推荐

NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018;

不推荐

NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil];
NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill", @"Mobile Web", nil];
NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];
NSNumber *buildingZIPCode = [NSNumber numberWithInteger:10018];

使用NS_ENUM

基础类型枚举使用NS_ENUM

typedef NS_ENUM(NSUInteger, PGOrderCashbackStatus) {
PGOrderCashbackStatusPay = 1,
PGOrderCashbackStatusWait = 2,
};
  • 更好的兼容swift
let status: PGOrderCashbackStatus = .pay

字符串枚举使用NS_STRING_ENUM

typedef NSString *ViewControllerKey NS_STRING_ENUM;
FOUNDATION_EXPORT ViewControllerKey const ViewControllerKeyTitle;
FOUNDATION_EXPORT ViewControllerKey const ViewControllerKeySubtitle;
FOUNDATION_EXPORT ViewControllerKey const ViewControllerKeySummary;
  • 更好的兼容swift
let type = ViewControllerKey.Title

集合

推荐

NSString *test = dictionary[@"test"];

不推荐

NSString *test = [dictionary objectForKey:@"test"];

使用initWithCapacity

[[NSDictionary alloc] initWithDictionary:2];
[[NSMutableArray alloc] initWithCapacity:2];
  • 当容易有明确大小时, 使用使用initWithCapacity创建

提示: 避免之后需要扩容, 提高性能, 减少内存消耗

添加nullable

NS_ASSUME_NONNULL_BEGIN
@property (nullable, nonatomic, readonly) UIView *superview;
- (CGPoint)convertPoint:(CGPoint)point toView:(nullable UIView *)view;
- (nullable __kindof UIView *)viewWithTag:(NSInteger)tag;
NS_ASSUME_NONNULL_END

对应的swift代码

var superView: UIView?
func convertPoint(point: CGPoint, view: UIView?) -> CGPoint {}
func viewWithTag(tag: Int) -> UIView?

添加NS_ASSUME_NONNULL_BEGIN/NS_ASSUME_NONNULL_END.

  • 默认都为nonnull, 不为空

提示: 主要是针对老文件, Xcode新版本会自动添加

  • Xcode会做静态检查, 比如给一个不可为空的参数/属性传递nil值会warning
  • 更好的兼容swift

常见问题

不添加NS_ASSUME_NONNULL_BEGIN/NS_ASSUME_NONNULL_END
  • 如果没有添加NS_ASSUME_NONNULL_BEGIN/NS_ASSUME_NONNULL_END, swift会直接当做强解包处理, 如果属性为nil会导致crash
属性/参数/返回值可能为nil, 不添加nullable标示.

添加NS_NOESCAPE

NS_NOESCAPE用于闭包参数声明, 保证函数内部不会持有该闭包, 不会有循环依赖的风险.

- (NSArray *)makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *))block {
    return [self mas_makeConstraints:block];
}
  • 默认编译器会当做逃逸闭包处理
  • 性能提升

编译器会做优化, 比如省略非必要的对self的捕获/保留/释放.

  • 更好的兼容swift

扩展: Apple @noescape提案

添加泛型

集合

@property(nonatomic,copy) NSArray<__kindof UIViewController *> *viewControllers;
NSDictionary *dictionary;
- (nullable __kindof UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath;
NSArray/NSDictionary/NSSet容器中的元素有明确的类型时, 添加泛型标识
__kindof

__kindof用来标识所有符合的子类都可以匹配到

  • Xcode可以做一些简单的编译检查, 当类型不一致时发出警告
NSMutableArray *strings = [NSMutableArray arrayWithCapacity:2];
[strings addObject:@""];// 
// Xcode警告 Incompatible pointer types sending 'NSNumber *' to parameter of type 'NSString * _Nonnull'
[strings addObject:@(2)];
// Xcode警告 Incompatible pointer types initializing 'UIView *' with an expression of type 'NSString *'
UIView *view = strings[0];
  • 更好的兼容swift
    不添加泛型标识, 在swift中为Any.

基础类型

NSInteger

尽可能使用NSInteger代替int/long, 除非必须.

BOOL

使用YES/NO, 不要使用true/false

扩展链接: ObjC的BOOL为什么要用YES、NO而不建议用true、false?

#define String @""
  • 尽可能减少宏的使用, 不兼容swift

import

不要import组件的umbrella.h头文件

#import  // 错误
  • 会导致引入更多头文件
  • 降低编译速度
  • 导致部分文件间接依赖某些头文件, 导致部分宏冲突
  • 尽可能import明确的的文件.h
#import 

提示: 特别是头文件中, 尽可能减少import

减少头文件import导入

  • 部分#import使用@class代替

提示: 避免导入更多头文件, 提高编译速度

推荐使用@import代替#import

添加#pragma mark -

@implement UIViewController () 

#pragma mark - LifeCycle

- (void)viewDidLoad {
    [super viewDidLoad];
}

#pragma mark - UITableViewDataSource

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 0;
}

#pragma mark - Private

- (void)test {}

#pragma mark - Getter

- (NSInteger)num {
    return 1;
}

@end
  • 添加#pragma mark分割同一个文件中的不同模块
  • 有利于Xcode Minimap提示


NS_ERROR

Handling Cocoa Errors in Swift

使用NSNotificationName

UIKIT_EXTERN NSNotificationName const UIApplicationWillEnterForegroundNotification 

NSNumber

使用BOOL/NSInteger代替NSNumber

  • 大部分场景并不需要使用到NSNumber对象, 值类型比对象性能更好. 除非你需要使用引用语义.

使用@()创建NSNumber/NSString

推荐
@(error.code)
@(error.code).stringValue
不推荐
[NSString stringWithFormat:@"%ld", (long)error.code]

不推荐使用+load/hook方法

尽可能避免使用+loadhook系统库相关的方法

提示: 如果一定要使用, 在iOS开发群里说一下. 并且在gitlab组件readme.md中备注.

总结

  • 尽可能规范代码风格, 保持一致的风格, 提高可读性.

扩展阅读: Effective Objective-C 2.0

扩展阅读: 禅与 Objective-C 编程艺术

扩展阅读: WWDC-Refine Objective-C frameworks for Swift

扩展: 关于命名和代码风格, 可以多看看iOS系统官方的库, 和一些开源库, 比如AFNetworking/SDWebImage/React Native.

待更新:推荐使用class属性代替方法

你可能感兴趣的:(Objective-C编码规范)