熟悉Objective-C
Objective—C通过一套全新的语法,在C语言基础上添加了面向对象的特性
频繁使用方括号和极长的方法名,使得代码十分易读。
了解Objective-C的起源
Obejective-C与C++,java等面向对象的语言类似,在语法上使用“消息结构”而不是“函数调用”
Objective-C由Smalltalk演化而来,Smalltalk是消息型语言的鼻祖。
//消息结构【Objective-C】
NSObject* obj = [NSObject new];
[obj performWith:paramter1 and:parameter2];
//函数调用 【C语言】
NSObject* obj = new NSObject;
obj->perfoem(parameter1, parameter2);
区别在于:==使用消息结构的语言,运行时执行的代码块由运行环境来决定,函数调用的语言,由编译器决定。==如果范例中调用的函数是多态的,运行时就要按照“虚方法表”(是编程语言为实现“动态派发”或“运行时方法绑定”采用的一种机制)来查出到底应该执行哪个函数实现。而采用消息结构的语言,不论是否多态,总是在运行的时候才会去查找要执行的方法。
Objective-C的重要工作都由“运行期组件”而非编译器来完成。使用Objective-C的面向对象特性所需的全部数据结构及函数都在运行期间组件里面。
Objective-C是C的“超集”,C语言的所有功能在编写Objective-C时仍然适用。要明白C语言中的“内存模型”,有助于理解Objective-C中的“引用计数”
OC语言中的指针是用来指示对象的,所有OC的对象都必须用指针声明。因为对象所占内存总是分配在“堆空间”而绝不会分配在“栈空间“
分配在堆中的内存必须直接管理,而分配在栈上用于报讯变量的内存则会在其栈帧弹出时自动清理。
Objective-C将堆内存管理抽象出来了,不需要malloc或free来分配或释放对象所占内存。被称为“引用计数”
在OC代码中,遇到的不含*的变量有可能使用“栈空间”,但是这些变量保存的不是OC对象。
在类的头文件中尽量少引入其他头文件
OC使用“头文件”和“实现文件”来区隔代码
- 使用Objective-C来编写“类”的标准方式为:以类名做文件名,分别创建两个文件,头文件后缀.h,实现文件后缀.m
- 使用Objective-C来编写任何类几乎都要引入“Foundation.h”。如果不在该类本身引入这个文件,那么就要引入与其超类所属框架对应的“基本头文件”。如:UIViewController的子类的头文件需要引入UIKit.h
- 在一个类A中需要有另一个类B的对象时,常规操作是给类A的.h文件包含类B的.h文件,“向前声明”该类指在类A的.h文件中并不包含类B的头文件,而是声明 @class B。这种方法适用于不需要知道类B的所有细节,只需要知道有一个类名叫B时。而类A的实现文件中,需要引入类B的头文件。将头文件的引入时机尽量延后,只在确定有需要时才引入,这样可以减少类的使用者所需要引入的头文件数量。向前声明解决了两个类相互引用的问题
如果在类A中有一个类B的属性,而在类B的实现中又用到了类A,如果在各自的头文件中引入对方的头文件,则会导致循环引用。解析一个头文件A时,编译器发现引入了另外一个头文件B,而头文件B又反过来引用头文件A,我们使用#import指令虽然不回导致死循环,但意味着这两个类里有一个无法正确编译。
- 但是有时候又必须引入其他头文件,如果类继承自超类,则必须引入定义那个超类的头文件,同理,如果要写的某个类遵从某个协议,那么该协议必须有完整定义,且不能向前说明。向前说明只能告诉编译器有某个协议,而此时编译器需要知道该协议中定义的方法。
- 有时候无法使用向前说明,比如要声明某个类遵守某一项协议,这种情况下,尽量把“该类遵循某协议”的这条声明移至“class-continuation分类”中。如果还不行,就把协议单独放在一个头文件里,然后引入。
多用字面语法,少用与之等价的方法。
- Objective-C已语法繁杂著称,事实上的确是这样,有一个非常简单的方法可以直接创建NSString对象。这就是“字符串字面量”。如果不使用这个方法,那么就要使用alloc和init方法来创建并初始化对象了。字面量语法也可以来声明NSNumber,NSArray,NSDictionary对象。使用字面量语法,可以缩减源代码长度,使其更为易读
NSString* str = @“Objective-C”;
字面量数值
有时候需要把整数,浮点数,布尔值存入Objective-C对象中,这种情况下可以用NSNumber类。
NSNumber* num1 = [NSNumber numberWithInt:1];
NSNumber* num2 = @1;
NSNumber* num3 = @2.5f;
NSNumber* num4 = @2002.6;
NSNumber* num5 = @YES;
NSNumber* num6 = @'A';
- 可以明显看到字面量语法更为简洁明了,声明中只包含数值,而没有多余的语法部分。
字面量数组
NSArray* array1 = [NSArray arrayWithObjects:@"111", @"222", @"333", nil];
NSArray* array2 = @[@"111", @"222", @"333"];
可以明显看到使用字面量来创建并初始化数组极其简单且易操作。
常规操作数组
- 数组的常规操作是使用objectAtIndex:方法。
NSString* str1 = [array1 objectAtIndex:1];
NSString* str2 = array1[1];
可以明显看到这种操作更为简洁,更易理解。而且和其他语法中使用下标来访问数组的方法类似。
- ==使用字面量方法来创建数组时,若数组元素对象中含有nil,则会抛出异常。==字面量语法相当于一种“语法糖”(指计算机语言中与另一套语法等效但是开发者用起来更加方便的语法,令程序更加易读,减少代码出错几率)。效果相当于先创建了一个数组,在将方括号内的所有对象都加入这个数组中。
id str = nil;
NSArray* array2 = @[@"111", str, @"333"];
NSLog(@"%@", array2);
如果我们这样来初始化数组,会出现一个报错
但是如果我们使用常见方法来创建这个数组,数组内就只包含一个元素,并不会抛出异常。这是因为我们使用字面量方法来创建数组时,实际上还是使用的arrayWithObjects:方法。这个方法会依次处理各个参数,直到遇到nil。这说明字面量方法更为安全有效,抛出异常另程序终止,总比数组里缺少元素要好。
字面量字典
字典是一种映射结构。传统的创建方法顺序为“对象”,“键”,“对象”,“键”,这种方法不易读懂,如果改用字面量,就非常容易理解了。
NSDictionary* dic = [NSDictionary dictionaryWithObjectsAndKeys:@"nuonuo", @"yes", @"guoguo", @"no", nil];
NSDictionary* dic2 = @{@"nuonuo" : @"yes", @"guoguo" : @"no"};
- 字面量的方法创建字典更为简洁,而且键出现在对象之前,理解起来更为顺畅
- 与数组一样,字面量创建字典如果中途遇到nil也会抛出异常
- 字典也可以像数组一样使用字面量方法来访问,增加代码可读性。
NSDictionary* dic = [NSDictionary dictionaryWithObjectsAndKeys:@"nuonuo", @"yes", @"guoguo", @"no", nil];
NSDictionary* dic2 = @{@"nuonuo" : @"yes", @"guoguo" : @"no"};
NSString* str1 = [dic objectForKey:@"no"];
NSString* str2 = dic[@"no"];
NSLog(@"%@ %@", str1, str2);
可变数组与字典
通过取下标操作,可以访问数组中某个下标或字典中某个键所对应的元素,如果数组与字典对象是可变的,那么也能通过下标修改其中元素值。
NSMutableArray* arr = [NSMutableArray arrayWithObjects:@"111", @"222", @"333", nil];
NSMutableDictionary* dic = [NSMutableDictionary dictionaryWithObjectsAndKeys:@"nuonuo", @"yes", @"guoguo", @"no", nil];
[arr replaceObjectAtIndex:1 withObject:@"444"];
[dic setObject:@"shenqi" forKey:@"yes"];
arr[1] = @"444";
dic[@"yes"] = @"shenqi";
局限性
字面量语法的使用,除了字符串外,创建出来的对象必须属于Foundation框架。如果自定义了这些类的子类,则无法使用字面量方法来创建
使用字面量创建出来的字符串,数组,字典等对象都是不可变的,若想要可变版本的对象,必须复制一份。
NSMutableDictionary* dic2 = [@[@1, @2, @3] mutableCopy];
多用类型常量,少使用#define预处理指令
如果在一个头文件中进行#define WIDTH 500的操作,那么预处理过程会将所有的WIDTH都换成500。所有引入了这个头文件的代码中的WIDTH都会被替换。
常量名称:若局限于某“编译单元”(实现文件)则在前面加上k,若常亮在类之外可见,则通常以类名为前缀
若不打算公开某个常量,则应该将其定义在使用该常量的文件里。
变量一定要同时用static和const同时修饰,如果试图修改由const修饰符所声明的常量,编译器会报错。static修饰意味着该变量仅在定义此变量的编译单元内可见。
实际上,如果一个变量声明即位const,又为static,那么编译器根本不会创建符号,而是像#define预处理指令一样,把遇到的所有变量替换为常值。
在头文件中使用extern来声明全局变量,并在相关文件中定义其值。这种常量要出现在全局符号表中,所以其名称应该加以区隔,通常用与之相关的类名做前缀。
用枚举表示状态,选项,状态码。
- 在以一系列常量来表示错误状态码或可组合的选项时,极易使用枚举为其命名。
- 枚举只是一种常量命名方式,某个对象所经历的各个状态就可以定义为一个简单的枚举集合。由于每种状态都使用一个便于理解的值来表示,所以这样写出来的代码更易读懂。编译器会为枚举分配一个独有的编号,从0开始,每个枚举值递增1。实现枚举所用的数据类型取决于编译器,不过其二进制位的个数必须完全能表示枚举编号。
- 在定义选项的时候,可以使用枚举类型。若这些选项可以彼此组合,则更应如此。只要枚举定义的对,各选项之间就可以通过按位或操作来组合,按位与操作可以判断出是否已启用某个选项。
- 可以把逻辑含义相似的一组状态码放入一个枚举集合中,而不要用#define预处理指令或常量来定义。
- switch语句里,也可以使用枚举来定义。然而,若是用枚举来定义状态机,最好不要有defult分支。这样的话,如果加入新枚举,编译器就会提醒开发者,switch语句并未处理所有枚举。