type
status
date
slug
summary
tags
category
icon
password
本系列是根据《Effective Objective-C 2.0》一书中的系列文章,选开发中实践的经验之谈,汇集于此,便于查阅。编排按《Effective Objective-C 2.0》中条目。
另外,不是书中有些我觉得不太适合的翻译会进行调整。比如“类族”替换成“类簇”。“惰性初始化”其实就是“懒加载”等等。
最佳实践
- 第2条:在类的头文件中尽量少引用其他头文件
- 第3条:多用字面量语法,少用与之等价的方法
- 第4条:多用类型常量,少用#define预处理指令
- 第5条:用枚举表示状态、选项、状态码
第2条:在类的头文件中尽量少引用其他头文件
@class
"向前声明"或“前向引用”,仅仅是声明一个类名,并不会包含类的完整声明。@class
还能解决循环包含的问题。在实现这个接口的实现类中,如果需要引用这个类的实体变量或者方法之类的,还是需要#import
把@class
中声明的类进来。那么为什么还需要
@class
呢?因为作声明某个类来用,编译器并不会将类的实例变量或方法引入,其可以加快编译,减少编译时间。另外,在实际中,会遇到——当解析某个文件时,编译器会发现它引入了另一个头文件,而那个头文件又回过头来引入了第一个头文件——循环包含,这时候,使用
#import
而非#include
指令虽然不会导致死循环,但却意味着两个类里有一个无法被正确编译。这时候,采用@class
仅作声明。#import
和#include
都能完整地包含某个文件的内容,#import
能防止同一个文件被包含多次;
#import <>
用来包含系统自带的文件,#import “”
用来包含自定义的文件
- 除非确有必要,否则不要引入头文件。一般来说,应在某个类的头文件中使用向前声明来提及别的类,并在实现文件中引入那些类的头文件。以此来尽量降低类之间的耦合。
- 要声明某个类遵循一项协议,精良移至“class continuation”分类中实现。如果不行,就把该协议单独放入一个头文件中,然后将其引入。
第3条:多用字面量语法,少用与之等价的方法
字面量语法,其实是
Objective-C 2.0
添加的“语法糖”,方便程序员书写,提高可读性以及编译时检查等特性。其中,涉及到类
NSString
、NSNumber
、NSArray
、NSDictionary
。字面量字符串
字面量数值
采用字面量语法,上面可写为:
字面量也适用于下述表达式:
字面量数组
使用字面量语法来创建:
假如,要声明一个
NSMutableArray
数组,可以采用下面:最后,需要注意的是,在用字面量语法创建数组时,若数组中有元素有nil,则会抛出异常,因为字面量语法实际上只是一种语法糖,其效果等同于是先创建了一个数组,然后把方括号内的多有对象都加到这个数组中。抛出的异常如下:
在改用字面量语法来创建数组时就会遇到这个问题,下面这段代码分别以两种语法创建数组:
假如,
object1
和object3
都指向了有效对象,而object2
是nil
,会出现什么情况?按字面量创建的arrayB会抛出异常,arrayA虽然能创建出来,但是其中只有
object1
一个对象。原因在于,“arrayWithObjects”方法会一次处理各个参数,直到发现nil
,object2
是nil
,所以方法提前结束。这个微秒的差别表明,使用字面量语法更为安全。抛出异常令应用程序终止执行,这比创建好数组之后才发现元素个数少了要好。
字面量字典
创建一个字典:
或者,采用更简洁的写法:
而采用字面量语法:
同样,在用字面量语法中,如果值是
nil
,与字面量创建数组表现相似,会抛出异常。下标操作
“取下标”操作一般会用
objectAtIndex
方法:假如,是可变数组,采用字面量写法:
字典的读写也类似。
第4条:多用类型常量,少用#define预处理指令
编写代码中常将常量写为:
预处理过程会把所有
ANIMATION_DURATION
一律替换成0.3
,假设该指令声明在某个头文件中,那么所有引入了这个头文件的代码,其ANIMATION_DURATION
都会被替换。更好的方式是:
首先,添加了类型信息,清楚地描述了常量的含义。
其次,要注意常量的名称,常用的命名法是:若常量局限于某“编译单元(一般只实现文件,即.m)”之内,则在前面加
k
;若常量在类之外可见,则通常以“类名”为前缀。最后,要注意常量的位置。我们总喜欢在头文件里声明预处理指令,这是相当糟糕的,当常量名称有可能互相冲突更是如此。由于OC没有“命名空间”这一概念,所以避免将常量声明放在头文件里。即使采用
static const
这种方式也是如此。若不打算公开某个常量,则应该将其定义在使用该常量的实现文件里。那么,为什么要用
static
和const
来修饰常量?static
表明的是作用域,意味着该变量尽在定义此变量的编译单元中可见,编译器每收到一个编译单元,就会输出一份“目标文件”。在Objective-C语境下,“编译单元”一次通常指每个类的实现文件。假如声明此变量时,不加static,那么编译器会为它创建一个“外部符号(external symbol)”。此时,若其他编译单元也声明了同名变量,就会抛出一条错误消息:const
则声明为不可修改。实际上,如果一个变量既声明为
static
,又声明为const
,那么编译器根本不会创建符号,而是会像#define
预处理指令一样,把所有遇到的变量头替换为常值,但是,这种方式具有类型信息。那么,假如要对外公开一个常量要怎么办?
对外公开常量
有时候,需要对外公开常量。常见的情景就是在类代码中调用
NSNotificationCenter
以通知他人。那么通知名一般声明一个外界可见的常值变量。此类变量需要放在“全局符号表(global symbol table)”中,以便可以在定义该常量的编译单元之外使用。其定义方式:
编译器发现头文件中含有
extern
,就知道,在全局符号表中将会有一个EOCStringConstant
的符号。即编译器无须查看其定义,就允许代码中使用此常量。因为它知道,当链接二进制文件后,肯定能找到这个常量。此类常量必须要定义,而且只能定义一次。通常将其定义在与声明该常量的头文件相关的实现文件里。由实现文件生成目标文件时,编译器会在“数据段”为字符串分配存储空间。
- 不要用预处理指令来定义常量,它不包含类型信息;
- 在实现文件中使用
static const
来定义“只在编译单元内可见的常量”。由于此类常量不在全局符号表中,所以无须为其名称加前缀;
- 在头文件中使用
extern
来声明全局变量,并在相关实现文件中定义其值。这种常量要出现在全局符号表中,所以其名称应加以区隔,通常用与之相关的类名做前缀。
第5条:用枚举表示状态、选项、状态码
在iOS开发中,凡是需要以按位或操作来组合的枚举都应使用
NS_OPTIONS
定义。若是枚举不需要互相组合,则应使用
NS_ENUM
来定义。- 应该用枚举来表示状态机的状态、传递给方法的选项以及状态码等值,给这些值起个易懂的名字;
- 如果把传递给某个方法的选项表示为枚举类型,而多个选项又可同时使用,那么就将各选项值定义为2的幂,以便通过按位或操作将其组合起来;
- 用
NS_ENUM
与NS_OPTIONS
宏定义了来定义枚举类型,并指明底层数据类型。以确保枚举是用开发者所选的底层数据类型实现出来的,而不会采用编译器所选的类型;
- 在处理枚举类型的
switch
语句中不要实现default
分支。这样的话,假如新枚举之后,编译器就会提示开发者:switch
语句并未处理所有枚举。
参考
链接
书籍
《Effective Objective-C 2.0》