iOS编码规范总结

作者: zzyong,网易 iOS 高级开发工程师。 请点击"阅读原文"查看作者更多文章。

  • Apple 编码指南:Apple CodingGuidelines

  • 一些基本的代码风格

1. 单个文件中保留相同的代码风格

// 不推荐

@property (nonatomic, strong) NSString *aString;

@property (strong, nonatomic) UIView *aView;

- (void)method1 {

//666

}

- (void)method2

{

//666

}

// 推荐

@property (nonatomic, strong) NSString *aString;

@property (nonatomic, strong) UIView *aView;

- (void)method1

{

//666

}

- (void)method2

{

//666

}

2. 搬砖不要偷懒

// 不推荐

@property (nonatomic) NSNumber *aNumber;

// 推荐

@property (nonatomic, strong) NSNumber *aNumber;

3. 合理的使用空格、换行和缩进保持代码美观

// 不推荐

-(void)myMethod{

if(1==a){

NSLog(@"%@",@"666");

}

b=1;

a=b>1?b:2;

c=a+b;

d ++;

}

// 推荐

- (void)myMethod {

if (1 == a) {

NSLog(@"%@", @"666");

}

b = 1;

a = (b > 1) ? b : 2;

c = a + b;

d++;

}

4. 命名和格式如果拿捏不准的话,可以参考苹果大佬的。请欣赏。

@property (nonatomic, assign) BOOL canScroll; // 大多数人

@property (nonatomic, assign) BOOL scrollEnable; // Apple

5. 代码对齐,让代码美如画

// 不推荐

static NSString *const kMyStringIsLongLong = @"kMyString";

static NSString *const kYourString = @"kYourString";

static NSString *const kHeString = @"kHeString";

//推荐

static NSString *const kMyStringIsLongLong = @"kMyStringIsLongLong";

static NSString *const kYourString = @"kYourString";

static NSString *const kHeString = @"kHeString";

6. 方法名过长时注意换行,以 : 进行对齐。此外方法的参数最好不要超过 6 个,方法实现控制在200行以内,太长会导致可读性变差

// 不推荐

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onEnterBackground) name: UIApplicationDidEnterBackgroundNotification object:nil];

// 推荐

[[NSNotificationCenter defaultCenter] addObserver:self

selector:@selector(onEnterBackground)

name: UIApplicationDidEnterBackgroundNotification

object:nil];

  • Notifications命名规则

    // 规则

    [Name of associated class] + [Did | Will] + [UniquePartOfName] + Notification

    // 示例

    UIKIT_EXTERN NSNotificationName const UIApplicationDidEnterBackgroundNotification;

  • 集合类带上存储类型(多使用泛型)

    // 不推荐

    @property (nonatomic, strong) NSMutableArray *childViewControllers;

    // 推荐

    @property (nonatomic, strong) NSMutableArray<UIViewController *> *childViewControllers;

  • Block里面的代码尽量不要超过 5 行,最好抽成一个方法。这样可以防止遗漏 weakSlef 可能导致的循环引用问题,其次在  self = nil 的情况下,可以减少多余的方法调用开销

    // 不推荐

    __weak typeof(self) weakSelf = self;

    [self queryMoreGameLivesWithCompletion:^(NSArray *rsp, BOOL success) {

    weakSelf.rsp = rsp;

    // ...

    //此处省略 N 行代码

    }];

    // 推荐

    [self queryMoreGameLivesWithCompletion:^(NSArray *rsp, BOOL success) {

    [weakSelf handleMoreGameLivesWithRsp:rsp success:success];

    }];

    - (void)handleMoreGameLivesWithRsp:(id)rsp success:(BOOL)success

    {

    self.rsp = rsp;

    // ...

    //此处省略 N 行代码

    }

  • 使用集合类安全方法:safe 前缀,防止越界和插入空对象等异常。注意区分外放和内测版本

    @implementation NSArray (Safe)

    - (id)safeObjectAtIndex:(NSUInteger)index

    {

    if (INTERNAL_VERSION) {

    return [self objectAtIndex:index];

    } else {

    if (self.count > index) {

    return [self objectAtIndex:index];

    }

    return nil;

    }

    }

    @end

    NSArray *infos = [NSArray array];

    // 不推荐

    id model = [infos objectAtIndex:index];

    //推荐

    id model = [infos safeObjectAtIndex:index];

  • UICollectionView 注册默认 Cell 和 SupplementaryView,防止发生异常

    [collectionView registerClass:[UICollectionViewCell class]

    forCellWithReuseIdentifier:@"UICollectionViewCell"];

    [collectionView registerClass:[UICollectionReusableView class]

    forSupplementaryViewOfKind:UICollectionElementKindSectionHeader

    withReuseIdentifier:@"header"];

    [collectionView registerClass:[UICollectionReusableView class]

    forSupplementaryViewOfKind:UICollectionElementKindSectionFooter

    withReuseIdentifier:@"footer"];

  • Delegate方法建议声明为  @required ,未实现方法时编译器会发出警告,有利于及时发现问题。如果为可选,在调用代理方法加上  respondsToSelector 判断

    // @optional

    if ([self.delegate respondsToSelector:@selector(myViewDidClick)]) {

    [self.delegate myViewDidClick];

    }

  • 使用 switch 语句时最好去除  default ,这样当有分支未实现时,编译器会发出警告,有利于及时发现问题。如果你对于警告不是很敏感的话,建议加上  default 分支,并且在  default 分支加上  NSAssert (断言),这样可以避免不必要的崩溃和及时发现问题。

  • 关键路径输出日志,有利于问题的发现与定位。例如一些较难重现的野指针崩溃,往往这些 Crash 日志的调用栈全是系统调用,此时如何是好?当然是结合用户日志分析一波,你可以通过日志定位户当时在哪个页面或者是分析其操作路径,这些都是有利于提高崩溃重现概率的点。

  • 避免同时使用 setNeedsLayout 和  layoutIfNeeded 。如需要重新布局只需要调用  setNeedsLayout 即可

    // 不推荐

    - (void)setUserInfo:(id)userInfo

    {

    _userinfo = userInfo;

    //...一顿操作

    [self setNeedsLayout];

    [self layoutIfNeeded];

    }

    // 推荐

    - (void)setUserInfo:(id)userInfo

    {

    _userinfo = userInfo;

    //...一顿操作

    [self setNeedsLayout];

    }

  • layoutSubviews(View) 和  viewDidLayoutSubviews(ViewController) 这个两个方法中进行视图布局。

    @implementation View

    - (void) layoutSubviews

    {

    [super layoutSubviews];

    // ...一顿操作

    }

    @end

    @implementation ViewController

    - (void)viewDidLayoutSubviews

    {

    [super viewDidLayoutSubviews];

    // ...一顿操作

    }

    @end


  • 一些轮播视图在其不可见时应该停止轮播,防止额外的开销

    // 视图是否可见判断条件

    if (viewController.isViewLoaded && viewController.view.window) {

    // viewController is visible

    }

    if (view.window) {

    //view is visible

    }

    // 推荐

    - (void)didMoveToWindow

    {

    [super didMoveToWindow];

    if (self.window) {

    [self startTimer];

    } else {

    [self stopTimer];

    }

    }



  • 使用 @[ ] 和  @{ } 创建集合对象时要确保容器内的 object 和 key 不为空。曾经遇到过 imageWithName: 返回为空导致的崩溃。

    // 不推荐

    @[[UIImage imageWithName:@"icon"]];

    // 推荐

    [NSArray alloc] initWithObjects:[UIImage imageWithName:@"icon"], nil];

    [NSDictionary alloc] initWithObjectsAndKeys:@(1) : @"key", nil];


  • 复杂视图应该遵循的 3 个原则

    1、应该继承 UIView,而不是 UICollectionViewCell 或者 UITableViewCell等特定类。这样有利于项目其他模块复用

    2、如果视图层级过于复杂,建议将视图进行分层,例如分为:top / middle / bottom,这样不仅使得视图的层级结构更加清晰,而且有利于问题定位

    3、复杂视图建议使用 代码创建 ,而不是  storyboard 或者  xib 。有利于后续维护和后来者接手。当然自己维护还好,但是后来者的话,看到满屏的线条和模糊不清的层级结构,心中已是苦不堪言

    1. 该继承谁?

    // 不推荐

    @interface MomentView : UICollectionViewCell

    @end

    // 推荐

    @interface MomentView : UIView

    @end

    2. 代码 or Xib,分层 ?

    // 不推荐

    @interface MomentView : UIView

    @property (nonatomic, weak) IBOutlet UIImageView *coverImageView;

    @property (nonatomic, weak) IBOutlet UIImageView *iconImageView;

    @property (nonatomic, weak) IBOutlet UILabel *nameLabel;

    @property (nonatomic, weak) IBOutlet UILabel *fansLabel;

    @property (nonatomic, weak) IBOutlet UICollectionView *picturesView;

    @property (nonatomic, weak) IBOutlet UILabel *titleLabel;

    @property (nonatomic, weak) IBOutlet UIButton *commentBtn;

    @property (nonatomic, weak) IBOutlet UIButton *likeBtn;

    @property (nonatomic, weak) IBOutlet UIButton *shareBtn;

    @end

    // 推荐

    @interface MomentView : UIView

    // top

    @property (nonatomic, strong) UIView *topContainerView;

    @property (nonatomic, strong) UIImageView *coverImageView;

    @property (nonatomic, strong) UIImageView *iconImageView;

    @property (nonatomic, strong) UILabel *nameLabel;

    @property (nonatomic, strong) UILabel *fansLabel;

    // bottom

    @property (nonatomic, strong) UIView *bottomContainerView;

    @property (nonatomic, strong) UICollectionView *picturesView;

    @property (nonatomic, strong) UILabel *titleLabel;

    @property (nonatomic, strong) UIButton *commentBtn;

    @property (nonatomic, strong) UIButton *likeBtn;

    @property (nonatomic, strong) UIButton *shareBtn;

    @end

  • 如果是为了查找集合内的某个特定对象,查找完毕后记住 break , 防止多余循环。我相信大多少人都知道是应该这样做,但是我就是忘了呀,所以在 bug 不多的时候多回去看看自己的代码,或者是互相 review 代码。

    // 不推荐

    UIView *videoPreView = nil;

    for (UIView *subview in self.subviews) {

    if ([subview isKindOfClass:[VideoView class]]) {

    videoPreView = subview;

    }

    }

    // 推荐

    UIView *videoPreView = nil;

    for (UIView *subview in self.subviews) {

    if ([subview isKindOfClass:[VideoView class]]) {

    videoPreView = subview;

    break;

    }

    }

  • Category 增加关联属性时避免直接声明  property ,应该使用对应的存取方法代替。这样可以避免由于归档时将该变量存入本地数据库,防止版本更新可能导致数据类型不对的异常。例如:V1.0 myObject 是 NSNumber,在 V2.0时由于 XX 原因改为 NSString。如果测试没有覆盖到,那么上线你会哭

    @interface NSObject (Extension)

    // 不推荐

    @property (nonatomic, strong) NSNumber *myObject;

    // 推荐

    - (NSObject *)myObject;

    - (void)setMyObject:(NSObject *)myObject;

    @end

  • 使用第三方库时应该独立封装一个中间层进行管理,避免直接调用,不然以后更换第三方库的时候你会崩溃。

// 举例 SDWebImage

// 不推荐

[self.coverImgView sd_setImageWithURL:[NSURL URLWithString:@"http://xxx.com/my.png"]

placeholderImage:[UIImage imageNamed:@"default"]];

// 推荐

@interface UIImageView (CCWebCache)

- (void)cc_setImageWithURL:(nullable NSURL *)url

placeholderImage:(nullable UIImage *)placeholder;

@end

[self.coverImgView cc_setImageWithURL:[NSURL URLWithString:@"http://xxx.com/my.png"]

placeholderImage:[UIImage imageNamed:@"default"]];

  • 使用 reloadSections 时一定要确保其他  section 的数据不会改变,如果性能影响不大的话,建议使用  reloadData

  • 当用到多层 for 循环时一定要慎重考虑,想想有没有更好的方法。因为往往他就是性能杀手

    // 好好想想有没有更好的方法

    for (int a = 0; a < MAX; a++) {

    for (int b = 0; b < MAX; b++) {

    for (int c = 0; c < MAX; c++) {

    //...一顿操作

    }

    }

    }

  • 常量定义建议使用 static const 代替  #define 。  define 只是纯粹的文本替换,没有类型安全检测, 而且宏重定义编译器并不会报错。虽然编译器有类型检测警告,但是你忽略了呢?那必定是强势背锅。

    //1. define

    // 类型错误 警告 :warning:

    #define MY_STRING @666

    // 重定义 警告 :warning:

    #import "People.h"

    #define MY_STRING @"myString"

    @interface People : NSObject

    @end

    // 有一天你的同事隔壁老王也定义一个全局同名的 MY_STRING 宏,此时你的宏就有可能被覆盖,杯具就开始了

    #define MY_STRING @"geBiLaoWang"

    //2. static const

    // 类型错误 --> Error!!!

    static NSString *const kMyString = @1;

    // 变量重名 --> Error!!!

    static NSString *const kMyString = @"myString";

    extern static NSString *const kMyString;

    #import "People.h"

    static NSString *const kMyString = @"peopleString";

  • 使用 CGRectGetXxx 代替  view.frame.xx.xx

    // 不推荐

    self.frame.size.width;

    self.frame.origin.x;

    self.frame.origin.y;

    // 推荐

    CGRectGetWidth(self.frame);

    CGRectGetMinX(self.frame);

    CGRectGetMinY(self.frame);

  • 文本计算使用 sizeWithAttributes: 代替  sizeToFit 。原因是 sizeToFit 开销大,会影响滑动性能。  sizeToFit 不但把文本宽高计算出来,而且还帮你把  frame 都一起设置了。由于你还是需要手动设置 frame 的,所以相当于多设置一次 frame , 而且  sizeToFit 背后做的事情远不止这些,具体参考  instruments 调用栈。

    // 不推荐

    - (void) layoutSubviews

    {

    [super layoutSubviews];

    [self.titleLabel sizeToFit];

    self.titleLabel.frame = CGRectMake(6, 6, CGRectGetWidth(self.titleLabel.frame), 6);

    }

    // 推荐

    - (void) layoutSubviews

    {

    [super layoutSubviews];

    CGFloat title_w = ceil([_titleLabel.text sizeWithAttributes:@{NSFontAttributeName : _titleLabel.font}].width);

    self.titleLabel.frame = CGRectMake(6, 6, title_w, 6);

    }

  • 手动布局时不应该用 .x .y .top .bottom 等便捷方法,这样会导致多次设置视图 frame,导致多余开销,影响滑动性能。正确的姿势应该是直接 setFrame:

    @interface UIView (CCFrame)

    @property (nonatomic, assign) CGFloat x;

    @property (nonatomic, assign) CGFloat y;

    @property (nonatomic, assign) CGFloat widtf;

    @property (nonatomic, assign) CGFloat height;

    @end

    UIView *aView = [[UIView alloc] init];

    // 不推荐

    aView.x = 2;

    aView.y = 6;

    aView.width = 66;

    aView.height = 66;

    // 推荐

    aView.frame = CGRectMake(2, 6, 66, 66);

  • 当 UIView 发生 Misaligned (像素未对齐)时怎么办?快用  CGRectIntegral( ) , 如果视图的 frame 已是整数值了,确定不会有 Misaligned,那问题很大可能出在其  superView 上,此时只需要对其父视图做一次  CGRectIntegral 即可。Misaligned 经常会出现在 UILabel 和 UIImageView 上。

    Misaligned

    1. 影响:

    视图或图片的点数(point),不能换算成整数的像素值(pixel),导致显示视图的时候需要对没对齐的边缘进行额外混合计算,影响性能

    2. 表现

    洋红色: UIView 的 frame 像素不对齐,即不能换算成整数像素值

    黄色: UIImageView 的图片像素大小与其 frame.size 不对齐,图片发生了缩放造成

    // 示例

    CGFloat title_w = [_titleLabel.text sizeWithAttributes:@{NSFontAttributeName : _titleLabel.font}].width;

    self.titleLabel.frame = CGRectIntegral(CGRectMake(6, 6, title_w, 6));

    // 至于 image 的话,如果是本地图片,UIImageView 尺寸大小设置为 image 尺寸即可

    // 如果是从服务器下载的图片,那将下载到的图片缩放到与 UIImageView 对应的尺寸,再显示出来

    // 但是要注意图片缩放也是一笔额外开销,所以对于频繁变动的 image 不建议这样做。因为图片缩放可能影响更大

    // 基于之前的列表优滑动化经验发现 Misaligned 对滑动性能影响较小,可有可无,所以就让他去吧

    // 如果是几乎不会变动的才考虑这样做,并且把处理后的图片缓存起来。

  • 当一个文件引入的头文件 很多 时,建议按照头文件类型进行分类,如果你是有追求的人,你还可以严格按照头文件长度再次排序。如下

    // controller

    #import "Controller1.h"

    #import "Controller2.h"

    #import "Controller3.h"

    // model

    #import "Model1.h"

    #import "Model2.h"

    // view

    #import "View1.h"

    #import "View2.h"

    // other

    #import "Other1.h"

    使用 @class 和  @protocol 代替  #import ,有利于加快编译速度

    // 不推荐

    #import "Dog.h"

    // 推荐

    @class Dog;

    @interface People : NSObject

    @property (nonatomic, strong) Dog *dog;

    @end

  • 如果公有属性是只读的,建议加上修饰符 readonly

    @interface People : NSObject

    // 不推荐

    @property (nonatomic, strong) NSString *name;

    // 推荐

    @property (nonatomic, strong, readonly) NSString *name;

    @end

  • 如果 Custom View 需要调用者传多个参数进行 UI 初始化,此时最好不要使用 多属性赋值 ,而是封装成一个方法给调用者,这样可以避免属性赋值顺序等问题。这里强调  初始化 ,一些特殊的 UI 更新除外

    @interface MyView : UIView

    // 不推荐

    @property (nonatomic, strong) NSString *title;

    @property (nonatomic, assign) int type;

    @property (nonatomic, assign) CGFloat *topMargin;

    // 推荐

    - (void)setTitle:(NSString *)title

    type:(int)type

    topMargin:(CGFloat *) topMargin;

    @end


  • 如何合并多个服务器请求?这里推荐 位掩码 (BitMask),好处就是代码简洁易懂,计算速度快等

    // 不推荐

    dispatch_group_t _queryGroup = dispatch_group_create();

    dispatch_group_async(_queryGroup, dispatch_get_main_queue(), ^{

    // Mine

    });

    dispatch_group_async(_queryGroup, dispatch_get_main_queue(), ^{

    // Fans

    });

    dispatch_group_notify(self.taskGroup, queue, ^{

    // 完成请求 666

    });

    // 推荐

    typedef NS_OPTIONS(NSUInteger, InfoQueryOptions) {

    InfoQueryNone = 0,

    InfoQueryMine = 1 << 0, // mine

    InfoQueryFans = 1 << 1 // fans

    };

    @property (nonatomic, assign) InfoQueryOptions queryOptions;

    __weak typeof(self) weakSelf = self;

    [self queryInfoMineWithCompletion:^(id *rsp, BOOL success) {

    weakSelf.queryOptions |= InfoQueryMine;

    [weakSelf reloadDataIfNeed];

    }];

    [self queryInfoFansWithCompletion:^(id *rsp, BOOL success) {

    weakSelf.queryOptions |= InfoQueryFans;

    [weakSelf reloadDataIfNeed];

    }];

    - (void)reloadDataIfNeed

    {

    if ([weakSelf isInfoQueryFinished]) {

    //一顿操作

    }

    }

    - (BOOL)isInfoQueryFinished

    {

    return (self.queryOptions & InfoQueryMine == InfoQueryMine) &&

    (self.queryOptions & InfoQueryFans == InfoQueryFans)

    }

  • 如何处理 Massive viewControlle(MVC 痛点),暂且定义大于 1500 行代码就算臃肿。这边推荐 Extension 和 Category 进行功能模块划分。该方法同样适用于其他类

    @interface MyViewController : UIViewController

    @end

    1. CollectionView 模块 (DataSource / Delegate)

    @interface MyViewController ()

    // 引入主类必要的一些属性或方法

    - (void)reloadDataIfNeed;

    @end

    @interface MyViewController (CollectionView) <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>

    - (void)setupCollectionView;

    @end

    2. 数据请求模块

    @interface MyViewController ()

    // 引入主类必要的一些属性或方法

    @property (nonatomic, assign) BOOL isQuerying;

    @property (nonatomic, strong) NSArray<InfoModel *> *sectionsData;

    @end

    @interface MyViewController (DataQuery)

    - (void)queryMineList;

    - (void)queryfansList;

    @end

    3. 数据上报模块

    @interface MyViewController ()

    // 引入主类必要的一些属性或方法

    @end

    @interface MyViewController (EventReport)

    @end

如果感觉这篇文章不错可以点击在看:point_down:

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章