一、了解Objective-C语言
1> Objective-C的起源是Smalltalk,使用的是”消息结构”而非”函数调用”
两者的区别:
1.前者运行时所执行的代码由运行环境来决定,无论是否多态,总是在运行时才会去查找所要执行的方法(动态绑定)。 而后者也是由编译器决定,如果在运行时就会按照”虚方法表”来查出到底应该执行那个函数实现
2> Objective-C的重要工作都由“运行期组件”而非编译器来完成。使用OC的面向对象特性所需的全部数据结构及函数都在运行期组件里面
eg:
运行期组件中含有全部内存管理方法。运行期组件本质上就是一种与开发者所编代码相链接的”动态库“,其代码能把开发者编写的所有程序粘合起来。好处:
只需要跟新运行期组件,便可提升应用程序的性能
3> 对象所占内存总是分配在“堆空间”
NSString *someString = @"The string";
NSString *anotherString = someString;
此内存图演示了一个分配在堆中的NSString实例,有两个分配在栈上的指针变量指向该实例
分配在堆中内存必须直接管理,而分配在栈上用于保存变量的内存则会在其栈帧弹出时自动清理。Objective-C将内存管理抽象出来,不需要用malloc及free来分配或释放对象所占内存
二、在类的头文件中尽量少引入其他头文件
1> 在.h文件中引入一个类的时候直接@class XXX(向前声明)就行,只需要告诉编译器有一个类名叫XXX。而在.m文件中则需要#import ”XXX.h“,因为使用后者必须知道其所有接口细节。而且向前声明也解决了两个类相互引用的问题
2> 有的时候必须要在头文件中引入其他头文件。比如某个类继承自某个超类,则必须引入定义那个超类的头文件。同理,如果写的类遵从某个协议,那么该协议必须完整定义,且不能使用向前声明。向前声明只能告诉编译器有某个协议,而此时编译器却要知道该协议中定义的方法。
// eg:
#import "EOCShape.h"
#import "EOCDrawable.h"
@interface EOCRectangle : EOCShape <EOCDrawable>
@property (nonatomic, assign) float width;
@property (nonatomic, assign) float height;
@end
此时无法使用向前声明,要声明某个类遵循某个协议。这种情况下,尽量把“该类遵循某协议”的 这条声明移至“class-comtinuation分类”中。如果不行的话,就把协议单独放在一个头文件中。然后将其引入。
3> 循环引用解决方案
非ARC:一端用retain,一端用assign
ARC:一端用strong,一端用weak
三、多用字面量语法,少用与之等价的方法
1>在用字面量语法创建数组或字典时,要确保值里不包含nil,否则会抛出异常
2>字面量语法有个小小的限制,就是除了字符串以外,所创建出来的对象必须属于Foudation框架才行。
3>使用字面量语法创建出来的字符串、数组、字典对象都是不可变的。若想要可变版本的对象,则需要复制一份。
4>应该通过去下标操作访问数组下标或字典中的键所对应的元素
四、多用类型常量,少用#define预处理指令
1>常用的命名法是:若常量局限于某编译单元(.m文件)之内,则在前面加字母k;若常量在类之外可见,则通常以类名为前缀
static const NSTimeInterval kAnimationDuration = 0.3;
2> 变量一定要同时用static和const声明。如果试图修改由const修饰符所声明的变量,那么编译器就会报错。而static修饰符则 意味着该变量仅在定义此变量的编译单元中可见。编译器每收到一个编译单元,就会输出一份“目标文件”(.o文件)
假如声明此变量时不加static,则编译器会为它创建一个“外部符号”。此时若是另一个编译单元中也声明了同名变量,那么编译器就抛出错误信息。
实际上,如果一个变量既声明为static,又声明为const,那么编译器根本不会创建符号,而是会像#define预处理指令一样,把所有遇到的变量都替换为常值。不过,这种方式定义的常量带有类型信息。
3>另外一种定义方法:
// .h文件
extern NSString *const EOCStringConstant;
// .m文件
NSString *const EOCStringConstant = @"value";
extern关键字:这个关键字要告诉编译器,在全局符号表中将会有一个名叫EOCStringConstant的符号。编译器无需查看定义,既允许代码使用此常量。因为当链接成二进制文件后,编译器肯定能找到这个常量。
此类常量必须要定义,而且只能定义一次。通常将其定义在与声明该常量的头文件相关的实现文件里。由实现文件生成目标文件,编译器会在“数据断”为字符串分配存储空间。链接器会把此目标文件与其他目标文件相链接,以生成最终的二进制文件。凡是用到EOCStringConstant这个全局符号的地方,链接器都能将其解析。
注意常量的名字。为避免名称冲突,最好是用与之相关的类名做前缀。
定义常量要优于使用#define预处理指令,因为编译器会确保常量值不变。而采用预处理指令所定义的常量可能会无意中遭人修改,从而导致应用程序各个部分所使用的值互不相同。
4> const的使用其他注意点:
- 当const修饰一个变量时,该变量会变成常量(不可修改,只读)
- 当const修饰指针变量时,有两种修饰情况:
1.const放在*前面
// const的修饰的*p1和*p2,*p1和*p2是常量,不能通过*p1、*p2间接修改其他变量的值
const int *p1 = &age;
int const *p2 = &age;
2.const放在*后面
// const修饰的p3,p3是个常量,p3不能再指向其他变量
i nt * const p3 = &age;
5> 用const类型常量和#define xxx的区别
#define age 10,在编译的时候,编译器会把程序里面所有的age替换成10这个字面量,所以一个字面量就代表着一份内存空间。
而const int age = 10;在程序中age始终只要一份age这个常量。所以两者在性能上,const常量要好一些
五、用枚举表示状态、选项、状态码
1> C++11标准修订了枚举的某些特性。其中一项改动为:可以指明用何种“底层数据类型”来保存枚举类型的变量。这样做的好处是,可以向前声明枚举变量了。若不指定底层数据类型,则无法向前声明枚举类型,因为编译器不清楚底层数据类型的大小,所以在用到此枚举类型时,也就不知道究竟该给变量分配多少空间。
2>用枚举定义:
typedef NS_ENUM(NSInteger, BSTopicType) {
BSTopicTypeAll = 1,
BSTopicTypeVideo = 41,
BSTopicTypeVoice = 31,
BSTopicTypePicture = 10,
BSTopicTypeWord = 29
};