熟悉Objective-C
- 第1条:了解Objective-C语言的起源
- 第2条:在类的头文件中尽量少引入其他头文件
- 第3条:多用字面量语法,少用与之等价的方法
- 字面数值
- 字面量数组
- 字面量字典
- 可变数组和字典
- 局限性
- 总结
- 第4条:多用类型常量,少用#define预处理指令
- 总结
- 第5条:用枚举表示状态、选项、状态码
- 总结
Objective-C是扩充C的面向对象编程语言,它在C语言的基础上添加了面向对象的特性。
今天学了关于Objective-C的一些知识与特性,基本上可以从五个方面来让我们熟悉Objective-C这门语言:
(Objective-C通常写作ObjC或OC和较少用的Objective C或Obj-C,所以夏目安我就用OC来代替。)
第1条:了解Objective-C语言的起源
- OC与C++,Java等面向对象语言类似,但是在很多方面却也不太一样。关于OC的介绍可以概括为以下几点:
==OC使用“消息结构”(messaging structure)而非“函数调用”(function calling)。OC语言由Smalltalk演化而来,后者是消息型语言的鼻祖。举个例子他们之间的区别可以是这样的:
//Messaging(Objective- C)
Object *obj = [Object new];
[obj perforWith: parameter1 and:parameter2];
//Function calling(C++)
Object *obj = new Object;
obj->perform(parameter1,parameter2);
关键的区别在于:使用消息结构的语言,其运行时所应该执行的代码由运行环境来决定;而使用函数调用的语言,则由编译器来决定。
2. OC语言声明的对象所占内存总是分配在“堆空间”(heap space)中,而绝不会分配在“栈”(stack)上。不能在栈中分配OC对象,分配在堆中的内存必须直接管理,而分配在栈上用于保存变量的内存则会在其栈帧弹出时自动清理。
3. 在OC代码中,也不是所有的定义都是通过“*”来定义,有时候会遇到定义里不含“ *”的定义,那么它们可能会使用到“栈空间”来储存,这些变量所保存的并不是OC对象。比如CoreGraphics框架中的CGRect就是个例子:
CGRect frame;
frame.origin.x = 0.0f;
frame.size.width = 100.0f;
//其中CGRect是C的结构体,其定义是:
struct CGRect {
CGPoint origin;
CGSize size;
};
typedef struct CGRect CGRect;
- OC为C语言添加了面向对象的特性,是其超集。OC使用动态绑定的消息结构,也就是说,在运行时才会检查对象类型。接收一条消息之后,究竟应执行何种代码,由运行期环境并非编译器决定。
- 理解C语言的核心概念有助于写好OC程序。尤其要掌握内存模型与指针。
第2条:在类的头文件中尽量少引入其他头文件
除非确有必要,否则不要引入头文件,一般来说在头文件使用向前声明取代引入,除非因为要实现属性、实例变量或者遵循协议而必须引入头文件,则应尽量将其移至“class-continuation分类”中。这样做不仅可以缩减编译时间,而且还能降低彼此依赖程度。
向前声明的语法为:
@class();
1.一般来说,应在某个类的头文件中使用向前声明来提及别的类,并在实现文件中引入那些类的头文件。
2.有时无法使用向前声明,比如要声明某个类遵循一项协议。这种情况下,尽量把“类遵循某协议”的这条声明移至“class-continuation分类”中,如果不行的话就把协议单独放在一个头文件中,然后将其引入。
第3条:多用字面量语法,少用与之等价的方法
其实说简单点,就是写代码的时候将代码写的简单,使用字面量语法就可以做到缩减源代码长度,使其更为易读。
字面数值
有时需要把整数、浮点数、布尔值封入OC对象中,这种情况下就可以用NSNumber类,该类可以处理多种类型的数值。不使用字面量语法的语句是这样:
NSNumber *someNumber = [NSNumber numberWithInt:1];
而使用字面量语法就是这样:
NSNumber *someNumber = @1;
其余还可以用字面量语法定义的数值还有:
NSNumber *intNumber = @1;
NSNumber *floatNumber = @2.5f;
NSNumber *doubleNumber = @3.14159;
NSNumber *boolNumber = @YES;
NSNumber *charNumber = @'a';
//还可以使用以下表达式:
int x = 5;
float y = 6.32f;
NSNumber *expressionNumber = @(x * y);
字面量数组
数组是常用的数据结构。如果不用字面量语法,那么就要这样创建数组:
NSArray *animals = [NSArray arrayWithObjects:@"cat",@"dog",@"pig",@"tiger",nil];
使用字面量语法:
NSArray *animals = @[@"cat",@"dog",@"pig",@"tiger"];
那么我们去取其中的元素,不使用的话:
NSString *dog = [animals objectAtIndex:1];
使用的话:
NSString *dog = animals[1];
另外假如我们不使用字面量语法去定义数可能出现这样一种情况:
id object1 = /*...*/;
id object2 = /*...*/;
id object3 = /*...*/;
NSArray *arrayA = [NSArray arrayWithObjectives:object1,object2,object3,nil];
NSArray *arrayB = @[object1,object2,object3];
假如object1,object3都指向了有效的OC对象,但是object2是nil,那么arrayA中因为在定义的时候先存入了object1,然后由于object2是nil,第一种定义的方法遇到nil,就会停止,所以只含有object1一个对象。而用字面量语法创建的arrayB会抛出异常,也就是说因为数组的中间不能有nil,用字面量语法可以提前发现错误,我们就可以立刻改为正确的。
字面量字典
与字面量数组相似,我们可以通过字面量语法这样创建:
NSDictionary *personData = @{@"first" : @"Matt" , @"last" : @"Galloway" , @"age" : @28};
同时字典如果不小心用了空值对象,也会有抛出异常的情况。
字典按照特定键访问其值的传统做法为:
NSString *lastName = [personData objectForKey@"lastName"];
字面量语法为:
NSString *lastName = personData[@"lastName"];
可变数组和字典
修改可变数组和字典可以用取下标操作来写:
mutableArray[1] = @"dog"
mutableDictionary[@"lastName"] = @"Galloway";
局限性
字面量语法有个小小的限制就是除了字符串以外,所创建出来的对象必须属于Founcdation框架才行。如果定义了这些类的子类,则无法用字面量语法创建其对象。但一般不会有人这样做。
另外使用字面量语法创建出来的字符串、数组、字典对象都不是可变的(immutable)。若想要可变版本的对象,则需要复制一份:
NSMutableArray *mutable = [@[@1,@2,@3,@4,@5]mutableCopy];
总结
1. 应该使用字面量语法来创建字符串、数值、数组、字典。与创建此类对象的常规方法相比,这么做更加简明扼要。
2. 应该通过取下标操作来访问数组下标或字典中的键所对应的元素。
3用字面量语法创建数组或字典时,若值中有nil,则会抛出异常。因此,务必保证值里不含nil。
第4条:多用类型常量,少用#define预处理指令
我们都知道通过#define定义,是宏定义,假如某条通过宏定义的指令声明在某个头文件中,那么所有引入了这个头文件的代码,都会执行这条指令,将#define定义的常量全部改变。
所以我们不应该这样定义:
#define Animation_duration 0.3
应该这样定义:
static constant NSTimeInterval kAnimationDuration = 0.3;
总结
1.不要用预处理指令定义常量。这样定义出来的常量不含类型信息,编译器只会在编译前据此执行查找与替换操作。即使有人重新定义了常量的值,编译器也不会产生警告信息,这将导致应用程序中的常量值不一致。
2.在实现文件中使用static const来定义“只在编译单元内可见的常量”(translation-unit-specific constant)。由于此类常量不在全局符号表中,所以无须为其名称加前缀。
3.在头文件中使用extern来声明全局常量,并在相关实现文件中定义其值。这种常量要出现在全局符号表中,所以其名称应加以区隔,通常用与之相关的类名做前缀 。
第5条:用枚举表示状态、选项、状态码
因为OC基于C语言,所以C语言有的功能它都有。其中之一就是枚举类型:enum。
我们定义枚举类型可以为:
typedef enum EOCConnectionState EOCConnectionState;
EOCConnectionState state = EOCConnectionStateDisconnected;
因为有了宏定义,所以每次定义枚举类型时可以不用再写:enum
除此之外,我们现在可以指明用何种“底层数据类型”(underlying type)来保存枚举类型的变量。语法为:
enum EOCConnectionStateConnectionState : NSInteger;
在枚举成员没有被手工分配值的情况下,编译器默认第一个成员的值为0,第二个为1,依次递增。
所以我们也可以像这样:
enum EOCConnectionStateConnectionState {
EOCConnectionStateDisconnected = 1,
EOCConnectionStateConnecting,
EOCConnectionStateConnected,
};
上述代码手动给 EOCConnectionStateDisconnected赋值为1,所以后边的成员就从1开始依次递增。
在Objective-C语言中,Apple在iOS6中引入了两个宏来重新定义枚举类型(即:NS_ENUM与NS_OPTIONS),这两者在本质上并没有差别,都是用于定义枚举类型,但是在使用中NS_ENUM多用于一般枚举,而NS_OPTIONS则多用于带有移位运算的枚举,举个例子:
enum UIViewAutoresizing {
UIViewAutoreasizingNone = 0,
UIViewAutoreasizingFlexibleLeftMargin = 1 << 0,
UIViewAutoreasizingFlexibleWidth = 1 << 1,
UIViewAutoreasizingFlexibleRightMargin = 1 << 2,
UIViewAutoreasizingFlexibleTopMargin = 1 << 3,
UIViewAutoreasizingFlexibleHeight. = 1 << 4,
UIViewAutoreasizingFlexibleBottomMargin = 1 << 5,
}
移位运算就是:
比如:1<<3
就是将1的二进制向左移动3位,就从000001变成了001000也就是8。
总结
1.应该用枚举来表示状态机的状态、传递给方法的选项以及状态码等值,给这些值起个通俗易懂的名字。
2.如果把传递给某个方法的选项表示为枚举类型,而多个选项又可同时使用,那么就将各选项值定义为2的幂,以便通过按位或操作将其组合起来。
3.用NS_ENUM与NS_OPTIONS宏来定义枚举类型,并指明其底层数据类型。这样做可以确保枚举是用于开发者所选的底层数据类型实现出来的,而不会采用编译器所选的类型。
4.在处理枚举类型的switch语句中不要实现default分支。这样的话,加入新枚举之后,编译器就会提示开发者:switch语句并未处理所有枚举。