iOS编码规范总结

栏目: IT技术 · 发布时间: 5年前

作者: 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

iOS编码规范总结 如果感觉这篇文章不错可以点击在看:point_down:


以上所述就是小编给大家介绍的《iOS编码规范总结》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Web Analytics 2.0

Web Analytics 2.0

Avinash Kaushik / Sybex / 2009-10-26 / USD 39.99

The bestselling book Web Analytics: An Hour A Day was the first book in the analytics space to move beyond clickstream analysis. Web Analytics 2.0 will significantly evolve the approaches from the fir......一起来看看 《Web Analytics 2.0》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

SHA 加密
SHA 加密

SHA 加密工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具