iOS编码规范总结

一、空格

在适当的地方、适当地加空格、可以适当地提高代码可读性

你一定读过”紧凑型”的代码,一个字符挨着一个字符,如果不是会报错,可能类名和变量名之间都没有空格了。用大学 c 语言老师的话说:你们的大拇指是残疾了吗??

为了避免博客风格影响视觉,直接上图片吧。其实我不知道是不是有这样明确的编码规范,我只是看苹果的怎么写,我就怎么写,下面也只是举一些简单的例子。

二、计时器

NSTimer 替换为dispatch source timer

NSTimer 在主线程的 Runloop 里会在 Runloop 切换其它模式时停止,这时就需要手动在子线程开启一个模式为 NSRunLoopCommonModes 的 Runloop,并且释放不到位,会导致引用 Timer 的对象也得不到释放。 如果不想开启一个新的 Runloop,并且避免引用问题,可以用不跟 Runloop 关联的 dispatch source timer。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)createTimer {
__block int timeout = 60;
dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, global);

dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
timeout --;
if (timeout <= 0) {
dispatch_source_cancel(timer);
dispatch_async(dispatch_get_main_queue(), ^{
//do something after timer done.
});
}
});

dispatch_resume(timer);
}

更多关于GCD的用法,请见我的另一个总结GCD (Grand Central Dispatch)

三、URL字符串解析

分析URL字符串的时候,使用 NSURLComponents 类做解析,不要通过字符串截取等方式

我看很多人的代码,在解析别人传给他的URL 字符串时(例如自己的APP被拉起时),都是使用 NSString 的各种截取方法,然后再自己做判断,这是有点”危险”的做法。如果你自信逻辑严谨的话,那没的说,如果你不擅长或者懒得做异常处理, NSRangelocation 存不存在以及 length 越界问题,就可以把程序搞crash。所以建议不要这样做,使用 NSURLComponents 。文档NSURLComponents

看完文档就会知道,真的好方便。。。

  • 初始化就不多说了,看自己需要
1
2
3
4
5
6
// Initialize a NSURLComponents with all components undefined. Designated initializer.
- (instancetype)init;
- (nullable instancetype)initWithURL:(NSURL *)url resolvingAgainstBaseURL:(BOOL)resolve;
+ (nullable instancetype)componentsWithURL:(NSURL *)url resolvingAgainstBaseURL:(BOOL)resolve;
- (nullable instancetype)initWithString:(NSString *)URLString;
+ (nullable instancetype)componentsWithString:(NSString *)URLString;
  • 获取URL里的内容,简直不要太方便哦,URLEncode之后的参数都准备好了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@property (nullable, copy) NSString *scheme; // Attempting to set the scheme with an invalid scheme string will cause an exception.
@property (nullable, copy) NSString *user;
@property (nullable, copy) NSString *password;
@property (nullable, copy) NSString *host;
@property (nullable, copy) NSNumber *port; // Attempting to set a negative port number will cause an exception.
@property (nullable, copy) NSString *path;
@property (nullable, copy) NSString *query;
@property (nullable, copy) NSString *fragment;

@property (nullable, copy) NSString *percentEncodedUser;
@property (nullable, copy) NSString *percentEncodedPassword;
@property (nullable, copy) NSString *percentEncodedHost;
@property (nullable, copy) NSString *percentEncodedPath;
@property (nullable, copy) NSString *percentEncodedQuery;
@property (nullable, copy) NSString *percentEncodedFragment;

//begin ios 9.0
@property (readonly) NSRange rangeOfScheme;
@property (readonly) NSRange rangeOfUser;
@property (readonly) NSRange rangeOfPassword;
@property (readonly) NSRange rangeOfHost;
@property (readonly) NSRange rangeOfPort;
@property (readonly) NSRange rangeOfPath;
@property (readonly) NSRange rangeOfQuery;
@property (readonly) NSRange rangeOfFragment;

当然关于 NSRange 的获取,是从ios 9.0 开始有的,适配低版本的话,需要注意一哈。

  • 参数的集合,NSURLQueryItem 类呢就是通过 key 、value 来取参数们了
1
2
3
4
// API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0))
@property (nullable, copy) NSArray<NSURLQueryItem *> *queryItems;
// API_AVAILABLE(macosx(10.13), ios(11.0), watchos(4.0), tvos(11.0))
@property (nullable, copy) NSArray<NSURLQueryItem *> *percentEncodedQueryItems;

我之前不是特别理解,参数要用数组+Item的方式,直到遇到了那位给我传的URL里,有两个同名参数不同值的仁兄,我才更加体会到了苹果开发者的严谨。。。

四、 枚举

举一个简单的场景:聊天软件,会区分 单聊、群聊、聊天室、服务号等等等等 不同类型的会话,开发时在做页面间传值的时候,往往需要把类型传递下去来做不同的事情,怎么明确的传呢?如果这个项目是多人负责,每个人负责不同的模块,像下面这样写代码,时间久了谁也看不懂:

1
2
//0单聊,1群聊
@property(nonatomic,assign)NSInteger type;
1
vc.type = 0;

不要笑,你肯定也写过这样的代码,模块多了、时间久了,type 是什么意思?没人记得…

枚举就在这个时候发挥作用了:增加代码可读性。

1
2
3
4
5
6
7
8
typedef NS_ENUM(NSUInteger, ConversationType) {
//单聊
ConversationTypePRIVATE = 1,
//群组
ConversationTypeGROUP = 2,
//聊天室
ConversationTypeCHATROOM = 3
};

这样一来比起单薄的数字,是不是更见名知意了呢?而且 OC 中枚举定义值的时候,都是 枚举名 + 具体分类,例如我们写的这个枚举中单聊的定义就是 ConversationType + PRIVATE = ConversationTypePRIVATE。敲出来的时候好敲,哈哈。

有的同学可能会问了,应对类型是整型的 type 这个枚举是够用了,那用字符串来分类的呢?没错,这是个问题,尤其是和服务端对接久了,你会发现,他们使用 字符串 多过 数值类型。不急,我们来小探一下 OC 中枚举都怎么用的:

普通枚举

1、C 语言的枚举写法 enum
1
2
3
4
5
typedef enum{
ConversationTypePRIVATE = 1,
ConversationTypeGROUP = 2,
ConversationTypeCHATROOM = 3,
} ConversationType;
2、OC 整型枚举
1
2
3
4
5
6
7
8
typedef NS_ENUM(NSUInteger, ConversationType) {
//单聊
ConversationTypePRIVATE = 1,
//群组
ConversationTypeGROUP = 2,
//聊天室
ConversationTypeCHATROOM = 3
};
3、字符串枚举实现方案
1、基于普通枚举,C 语言实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//先定义一个普通的整型枚举
typedef NS_ENUM(NSUInteger, ConversationType) {
//单聊
ConversationTypePRIVATE = 1,
//群组
ConversationTypeGROUP = 2,
//聊天室
ConversationTypeCHATROOM = 3
};

//实现一个方法,实现里做 整型 和 字符串 的匹配
NSString *ConversationTypeString(ConversationType status) {
switch (status) {
case ConversationTypePRIVATE:
return @"private";
case ConversationTypeGROUP:
return @"group";
case ConversationTypeCHATROOM:
return @"chatroom";
default:
return @"";
}
}

//或者使用 一个 NSString * 类型的数组
NSString *ConversationTypeStringMap[] = {
[ConversationTypePRIVATE] = @"private",
[ConversationTypeGROUP] = @"group",
[ConversationTypeCHATROOM] = @"chatroom"
};

但这两种方式有一个弊端,就是不能像枚举类型一样直接作为一个类型 限定传参的类型。

2、宏定义

初期我使用宏定义比较多,每个类型值定义一个宏。。。这里不举例了。。。

3、苹果官方的做法
1
2
3
4
5
6
7
8
9
10
11
//.h 中 
typedef NSString *ConversationTypeKey NS_STRING_ENUM;

FOUNDATION_EXPORT ConversationTypeKey const ConversationTypeKeyPRIVATE;
FOUNDATION_EXPORT ConversationTypeKey const ConversationTypeKeyGROUP;
FOUNDATION_EXPORT ConversationTypeKey const ConversationTypeKeyCHATROOM;

//.m 中
NSString * const ConversationTypeKeyPRIVATE = @"private";
NSString * const ConversationTypeKeyGROUP = @"group";
NSString * const ConversationTypeKeyCHATROOM = @"chatroom";

相比会产生过多二进制文件的宏定义方式, 建议用FOUNDATION_EXPORT

位移枚举

位移枚举是指使用位移操作来进行枚举或标志的一种技术。在 OC 中,这通常涉及到使用枚举类型和位移运算符来表示一组相关的选项或标志。举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 定义一个枚举类型
typedef NS_OPTIONS(NSUInteger, MyOptions) {
MyOptionNone = 0,
MyOptionOne = 1 << 0, // 1左移0位,值为1
MyOptionTwo = 1 << 1, // 1左移1位,值为2
MyOptionThree = 1 << 2 // 1左移2位,值为4
};

// 使用枚举类型
MyOptions selectedOptions = MyOptionOne | MyOptionThree; // 使用位或运算符组合多个选项

// 检查某个选项是否被设置
if (selectedOptions & MyOptionOne) {
NSLog(@"Option One is selected");
}

// 检查多个选项是否被设置
if ((selectedOptions & (MyOptionTwo | MyOptionThree)) == (MyOptionTwo | MyOptionThree)) {
NSLog(@"Option Two and Three are both selected");
}

在这个示例中,我们定义了一个名为 MyOptions 的枚举类型,其中的每个选项都是通过左移位操作得到的。然后,我们可以使用位或运算符将多个选项组合在一起,以便在代码中轻松地表示和传递多个状态或选项。

请注意,这里使用的是 NS_OPTIONS 宏,它允许我们使用位移枚举,并且通常与NSUInteger一起使用。此外,我们还使用了位与运算符来检查某个选项是否已经被设置,以及使用位与运算符来检查多个选项是否同时被设置。