• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

iOS_Objective-C编程规范

武飞扬头像
Morgana_Mo
帮助3

学新通


1 格式

1.1 【必须】代码组织

  • 使用#pragma mark -将各 protocol 实现函数、功能相近的函数分组排放。
  • 函数定义前空一行。
#pragma mark - Initial Methods
#pragma mark - Override Methods
#pragma mark - Private Methods
#pragma mark - Public Methods
#pragma mark - Notifications
#pragma mark - Event Handlers

1.2 【推荐】换行

  • 一行代码不应超过 150 个字符,超过应该换行。
  • 豁免场景:不计算字符串内容的长度。
- (id<UIAdaptivePresentationControllerDelegate>)
    adaptivePresentationControllerDelegateForViewController:(UIViewController *)viewController;

- (void)presentWithAdaptivePresentationControllerDelegate:
    (id<UIAdaptivePresentationControllerDelegate>)delegate;

1.3 【推荐】函数长度

如果一个函数除空行和注释以外的内容超过了80 行,则可以思考,能否在不破坏程序结构的前提下,对函数进行拆分。

2 命名

2.1 【必须】类和协议名称

驼峰式命名:Upper camel case

类名:应该包含一个名词,该名词能清楚的表明类(或类的对象)的描述或者行为。跨应用使用的类和协议必须使用合适的前缀(例如:GTMSendMessage)。
协议名:通用的方式是使用动名词来命名协议。例如:NSLocking

2.2 【必须】分类

  • 分类名称前缀,表明分类属于哪个项目或模块,如NSString (GTMParsing)

  • 分类的方法前缀,避免和系统库/其他项目/其他模块的方法名称冲突,如gtm_myCategoryMethodOnAString:

2.3 【必须】文件名

文件的扩展名及其意义如下:
.h C/C /Objective-C 的头文件
.m Objective-C 实现文件
.mm Objective-C 实现文件
.hpp C 头文件
.cpp 纯 C 的实现文件
.c 纯 C 的实现文件

2.4 【推荐】缩略词和首字母缩写词

alloc:分配、dealloc:销毁、alt:轮流,交替、calc:计算
pboard:粘贴板(仅对常量)、horiz:水平 、vert:竖直
init:初始化、func:函数、msg:消息、info:信息、rect:矩形
Temp:临时、暂时、nib:interface builder 文档

计算机行业中存在一些首字母缩写词,推荐全大写(优先级高于驼峰命名法!!!)。常见的一些首字母缩写词如下:
ASCII、PDF、XML、HTML、URL、RTF、HTTP、TIFF
JPG、PNG、GIF、LZW、ROM、RGB
CMYK、MIDI、FTP、JSON、OS、ID

2.5 【必须】宏定义

宏命名请使用蛇式命名:shouty snake case,将全部字母大写并合理使用下划线分割单词。同时,类 C 函数风格的命名也是允许的。

#define QQ_DEBUG_BUILD ...             // GOOD
#define QQ_ASSERT_GT(X, Y) ...         // GOOD, 宏风格
#define QQAssertGreaterThan(x, y) ...  // GOOD, 函数风格,参数遵循驼峰命名
#define kIsDebugBuild ...               // AVOID
#define unless(X) if(!(X))              // AVOID

对于 Xcode 生成的头文件,默认会生成以#define filename_h命名的宏来防止多重包含。如:

// QQSharedDefine.h
#ifndef QQSharedDefine_h // 该宏来防止多重包含
#define QQSharedDefine_h
...
#endif /* QQSharedDefine_h */

2.6 【推荐】方法名

返回布尔值的 getter 命名应以 is/can/should 等开头,但属性名不应包含 is/can/should。

@property (nonatomic, getter=isGlorious) BOOL glorious;
- (BOOL)isGlorious;

BOOL isGood = object.glorious;      // GOOD.
BOOL isGood = [object isGlorious];  // GOOD.
BOOL isGood = object.isGlorious;    // AVOID.

2.7 【必须】变量与属性名

  • 局部变量属性命名首字母小写,采用驼峰命名法。

  • 文件范围全局变量使用 g 作为前缀!!!

static int gGlobalCounter;
  • 常量const全局和静态变量)应使用驼峰命名法,不要使用#define宏来定义常量。
    整型常量,尽量使用 const 或者枚举;浮点型常量,使用 const 定义。

错误处理需要定义常量时,推荐使用错误相关的类型 NSErrorDomain 和错误相关的枚举宏 NS_ERROR_ENUM

extern NSErrorDomain const QQServiceErrorDomain;
NS_ERROR_ENUM(QQServiceErrorDomain) {
    QQServiceErrorFileNotFound  = -9000,
    QQServiceErrorTimeout       = -9001,
};
  • 枚举使用 NS_ENUM
typedef NS_ENUM(NSInteger, QzoneFeedType) {
    QzoneFeedTypeFriends = 0,
    QzoneFeedTypeHomepage,
    QzoneFeedTypeBlog,
};
  • 位掩码使用 NS_OPTIONS
typedef NS_OPTIONS(NSUInteger, NYTAdCategory) {
    NYTAdCategoryAutos      = 1 << 0,
    NYTAdCategoryJobs       = 1 << 1,
    NYTAdCategoryRealState  = 1 << 2,
    NYTAdCategoryTechnology = 1 << 3
};

2.8 【推荐】通知和异常

  • 通知使用NSNotificationName作为类型,常量标识,其名称以这种方式组成:

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

UIKIT_EXTERN NSNotificationName const NSApplicationDidBecomeActiveNotification
UIKIT_EXTERN NSNotificationName const NSWindowDidMiniaturizeNotification
  • 异常名称由全局NSString对象标识,以这种方式组成:
    [Prefix] [UniquePartOfName] Exception(每部分首字母大写)
NSColorListIOException
NSColorListNotEditableException
NSDraggingException

3 注释

3.1 【推荐】文件注释

必须包含文件名,作者,创建时间,版权等信息,可以使用 Xcode 工程的默认模板。对文件内容的基本描述。

// QQObj.h
// 消息对应的数据结构
// Created by NAME on 2019/07/30
// Copyright (c) 2019年 Tencent. All rights reserved.
//

3.2 【推荐】声明部分的注释

函数接口应加以注释,以描述函数功能与参数定义,以及其他模块,文件的关系。属性,成员变量,协议等的声明必要时要加上注释。

如果已经在文件头部详细描述了接口,可以直接说明 “完整的描述请参见文件头部”。

对外暴露的所有接口都应该有注释来解释它的作用、参数、返回值。
对外暴露的接口应该在注释中说明线程安全性。如果类的实例可以被多个线程访问,记得注释多线程条件下的使用规则。

注:接口设计需要经可能的便于UT !!!(不要写无参数无返回值的接口)

3.3 【推荐】实现部分的注释

重要或复杂逻辑必须加上注释。

// Set the property to nil before invoking the completion handler to
// avoid the risk of reentrancy leading to the callback being
// invoked again.
CompletionHandler handler = self.completionHandler;
self.completionHandler = nil;
handler();

行尾注释应与代码分开至少 2 个空格,并保持对齐。

[self doSomethingWithALongName];  // Two spaces before the comment.
[self doSomethingShort];          // More spacing to align the comment.

4 函数与方法

4.1 【必须】基本原则

  • 参数个数越少越好,多于 6 个参数时建议考虑重构。

  • 函数的边界(参数的要求、返回值的范围、是否返回为空)要在注释中写明,且在代码中明确检查,包括断言及if判断。

  • 事件方法要写参数(如:xxxx:(UIButton *)sender )

4.2 【必须】修饰

属性的修饰:readonlynonullnullablenull_resettableget不为空,set可为空)、__null_unsepecified(不确定是否为空)

__kindof:当前类 or 其子类

属性:推荐使用上下文相关的非下划线关键字,例如 nonnullnullable
其他场景:推荐使用 _Nullable_Nonnull 关键字。

NS_ASSUME_NONNULL_BEGIN // Nonnull Audited Regions
@interface MOClass ()
// 声明属性修饰(必须)
@property (nonatomic, copy, readonly, nullable) NSString *aString;
@property (nonatomic, copy, readonly) NSString * _Nullable aString;
// 方法 返回值 和 参数 修饰(必须)
- (nullable NSString *)methodWithString:(nullable NSString *)aString;
- (NSString * _Nullable)methodWithString:(NSString * _Nullable)aString;
NSArray<GTMBook *> *_Nullable GTMLoadBooksFromFile(NSString *_Nonnull path);
@end
NS_ASSUME_NONNULL_END

可以使用区域设置( NS_ASSUME_NONNULL_BEGINNS_ASSUME_NONNULL_END)或可空性变量修饰符修饰参数。

注:弃用 __nullable__nonnull(苹果为了避免与第三方库潜在的冲突,把 __nullable__nonnull改成了_Nonnull/_Nullable


4.3 【必须】nil 检查

字符串判空:QLSafeString(str) (str?:@"")QNBSafeString(str) (str?str:@"")

nil 检查只用在逻辑流程中,避免逐行代码地在对象发消息前进行 nil 检查。对 nil 发送任何消息都是可以的。

存入NSArrayNSDictionary的数据要判空:!= nil && != NULL

4.4 【必须】点语法

建议使用点语法来访问或者修改 OC 类的属性,访问其他 OC 方法时首选方括号方式。

init 相关方法和 dealloc 里面不要用点语法!!!

4.5 【必须】使用轻量级泛型来记录容器的类型

// 使用 Xcode 7 及以上版本的所有项目都应该使用 Objective-C 轻量级泛型表示法来表明容器包含的对象。
@property (nonatomic, copy) NSArray<Location *> *locations;
@property (nonatomic, copy, readonly) NSSet<NSString *> *identifiers;

NSMutableArray<Location *> *mutableLocations = [otherObject.locations mutableCopy];

// 如果类型比较复杂,请考虑使用 typedef 来保持可读性。
typedef NSSet<NSDictionary<NSString *, NSData *> *> TimeZoneMappingSet;
TimeZoneMappingSet *timeZoneMappings = [TimeZoneMappingSet setWithObject:...];

// 如果类型不确定,使用 id 来声明。
@property (nonatomic, copy) NSArray<id> *unknowns;

4.6 【必须】异常的使用

(1)可以使用 @try/@catch/@finally/@throw 来进行异常处理。

(2)也可以通过返回值(nil, NULL, NO 或者 错误码

(3)或者传递一个 NSError 对象来返回错误。

鉴于使用异常的代价较高(安装包、退堆栈带来的性能开销,此外还可能引发内存泄露),条件允许时,应该优先使用 NSError 对象或者返回错误码形式,但对于第三方组件的代码,在使用时,应使用 @try/@catch 进行异常保护

对于后台返回的数据以及文件中读取的数据,应进行足够的校验与异常保护。包括但不限于对数据类型、长度进行校验,使用 @try/@catch 进行序列化,反序列化过程的保护等。

5 控制结构

5.1 【必须】分支结构

  • if-else结构不能超过四层
  • 条件分支中最快路径代码要放在最前面,可以有多个return
  • 所有的for,if,while等语法结构主体都必须用花括号,即使主体代码只有一行。

5.2 【可选】BOOL 陷阱

  • 将常规整数值转换为 BOOL,请使用三元运算符返回 YESNO 值。
    BOOL 使用逻辑运算符 (&&, ||! ) 是可以的,其返回值可以安全转换为 BOOL ,无需三元运算符。
- (BOOL)isBold {
  return ([self fontTraits] & NSFontBoldTrait) ? YES : NO;
}
- (BOOL)isValid {
  return [self stringValue] != nil;
}
- (BOOL)isEnabled {
  return [self isValid] && [self isBold];
}
// AVOID:
- (BOOL)isBold {
  return [self fontTraits] & NSFontBoldTrait;  // AVOID.
}
- (BOOL)isValid {
  return [self stringValue];  // AVOID.
}
  • 永远不要直接将 BOOL 变量与 YES 比较,返回值可能不如你所愿。BOOL定义为signed char,因此它可能具有除 YES (1) 和 NO (0) 之外的值。也没有必要将 BOOL 值与 NO 比较,使用if以及!进行判断会使代码更为直观。
BOOL great = [foo isGreat];
if (great) { }       // GOOD.
if (![someObject boolValue]) {}	// GOOD.
// AVOID:
BOOL great = [foo isGreat];
if (great == YES) { }  // AVOID. 永远别这么做
if ([someObject boolValue] == NO) { } // AVOID

6 类与对象

当创建NSStringNSDictionaryNSArray,和NSNumber类的不可变实例时,都应该使用字面量。

6.1 【必须】明确指定初始化方法、使用指定初始化方法(Designated Initializer)

6.2 【必须】重写指定初始化方法

对于需要继承你的类的人来说,明确指定初始化方法十分重要。这样他们就可以只重写一个初始化方法(可能是几个)来保证他们的子类的初始化方法会被调用。这也有助于将来别人调试你的类时,理解初始化代码的工作流程。

// 禁用 无效的 初始化方法
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithCoder NS_UNAVAILABLE;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;

// 指定初始化方法
- (instancetype)initWithFrame:(CGRect)frame
						 type:(NSInterger)type NS_DESIGNATED_INITIALIZER;

6.3 【必须】初始化函数简洁

6.4 【必须】保持公共 API 简单

7 Cocoa 相关

7.2 【必须】视图布局

  • 避免在界面布局中使用magic number,应使用能够说明用途的常量。

  • 建议在界面布局时使用相对布局,例如:

    1. 使用目标view在父view中的相对位置
    2. 使用目标view与相关view中的相对位置
    3. 使用目标view与相邻view中的相对位置
  • 当访问一个 CGRectxywidthheight 时,应该使用CGGeometry 函数代替直接访问结构体成员。

CGRect frame = self.view.frame;
CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);

8 单测相关

8.1、单例的mock

不能直接mock单例的,会引起mock冲突。
推荐的写法:

id center = OCMPartialMock([[QLLoginCenter alloc] init]); // 每次mock alloc 一个单例
OCMStub([[center classMethod] sharedInstance]).andReturn(center); // mock 它的 sharedInstance 方法

8.2、测试待Assert的代码:

BOOL executed = NO;
@try {
    executed = YES;
    NSUInteger invalidCount = [vc numberOfUnreadMessagesWithID:@"invalidBlockID"];
    XCTAssertEqual(invalidCount, 0);
} @catch (NSException *exception) {
    XCTAssertNotNil(exception);
}

9 补充:

9.1、extern用:FOUNDATION_EXPORT

9.2、更新布局

这个不可以直接调用layoutSubviews,可以用setNeedLayout,如果等不到下一次刷新可以调用layoutIfNeeded

9.3、更新subView布局

- (void)layoutSubviews {
  [super layoutSubviews];
  // TODO
}
- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];    
    if (button.superview == self.view) {
        [button sizeToFit]; // 可以这样获得自适应size
    }
}

9.4、synthesize/dynamic

// 系统默认实现
@synthesize propertyName = _propertyName;
// @dynamic 阻止自动合成

9.5、判断是否实现了指定协议的方法

[MOClass conformsToProtocol:@protocol(MOLockingProtocol)];
[vc conformsToProtocol:@protocol(MOLockingProtocol)];

9.6、IOC:inversion of control控制反转

如:Cell持有VM,但是VM不持有Cell;当VM需要通知Cell更新时,可以先注册Block,在需要时调用就好,就不会导致互相依赖这样高耦合的代码,即控制反转。

9.7、import头文件顺序

自身的头文件
系统库的头文件
开源第三方库的头文件
内部第三方库的头文件
模块内的头文件
项目内的头文件

不同类型的头文件中间最好空行,同类型的头文件尽量按照字母顺序排列

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfhjiia
系列文章
更多 icon
同类精品
更多 icon
继续加载