《Effective OC》读书笔记(一):熟悉Objectiv
第一条:了解Objective-C语言起源
OC和许多面向对象的语言类似,但还是有本质上的差别,OC使用了消息结构
,而不是函数调用
//Objective-C
Object *obj = [Object new];
[obj performWith:param and:param1];
//c++
Object *obj = new Object;
obj->perform(param,param1)
最关键的区别就是,采用消息结构的语言,其运行时所执行的代码由运行环境
来决定,而函数调用的语言,则由编译器
决定。函数调用的方法在运行时就已经确定了到底要执行哪个函数实现,而消息结构的语言则是在运行时才去查找要去执行的函数。
Objective-C的重要工作都由运行时组件
来完成,而不是编译器,Objective-C的面向对象特性所需的全部数据结构以及函数
都在运行时组件里面。
Objective-C语言中的指针是用来指示对象的,想要声明一个变量,令其指代某个对象,就如以下语法:
NSString *someString = @"the string";
它声明了一个名为someString的变量,其类型是NSString *
,也就是说此变量是指向NSString的指针
,对象所占的内存总是分配在“堆空间
”,而不会分配在“栈
”上,someString变量指向分配在堆里的某块内存,其中含有一个NSString对象,如果我们进行以下操作:
NSString *someString = @"the string";
NSString *anotherString = someString;
这两个变量会同时指向此对象,当前栈帧中分配了两块内存,每块内存的大小都能容下一枚指针,这两块内存的值都一样,就是NSString实例的内存地址。
OC代码中有时会遇到不含*的变量,它们可能会使用栈空间,这些变量保存的不是OC对象,比如CoreGraphics
中的CGRect
:
CGRect frame;
//CGRect是C结构体
struct CGRect{
CGPoint origin;
CGSize size;
}
typedef struct CGRect CGRect;
第二条:在类的头文件中尽量少引入其他头文件
与C++
一样,OC也是用“头文件”和“实现文件”来区隔代码,如果在一个类中引入了另一个类,则需引入其头文件:
#import "Test.h"
这种方法可行,但不够优雅,如果不需要知道Test类的全部细节,只需要知道有这样一个类就好我们可以使用向前声明:
@class Test;
但在实现文件中,我们需要知道Test文件的详细内容,则需要使用import来引入。将引入头文件的时机尽量延后,只在却有需要的时候才引入,这样就可以减少类的使用者所需引入的头文件数量,引入过多头文件会增加编译时间,获取有很多用不到的内容也被引入了。
有时无法使用向前声明,比如要声明某个类要遵循一项协议,这种情况下就把协议的声明迁移至“class-continuation分类
”中。
第三条:多用字面量语法,少用与之等价的方法。
从Objective-C1.0
开始就可以使用很简单的方法创建NSString
对象,这就是“字符串字面量”。
NSString *someString = @"Effective OC 2.0";
不用这种方法就要以常见的alloc
及init
方法来分配并初始化NSString
对象了,现在也可以使用这种方式来声明NSNumber,NSArray,NSDictionary
类的实例。这种方式可以缩减源码长度,使其更为易读。
有时需要把整数、浮点数、布尔值封装到对象中,这时可以使用NSNumber
类,该类可以处理多种类型的数值。
NSNumber *someNumber = [NSNumber numberWithInt:1];
我们还可以更简洁
NSNumber *someNumber = @1;
能够以NSNumber
实例表示的所有数据类型都可以使用该语法:
NSNumber *float = @2.5f;
NSNumber *double = @3.1415;
NSNumber *bool = @YES;
数组字典的创建:
NSArray *array = @[@"cat",@"dog"];
NSDictionary *dictionary = @{
@"name":@"Kevin"
}
另外要注意,使用字面量创建出来的字符串,数组,字典都是不可变的,要是想生成可变对象则需要复制一份:
NSMutableArray *m_Array = [@[@"1",@"2"] mutableCopy];
第四条:多用类型常量,少用#define预处理指令
我们经常会使用以下方式声明常量:
#define ANIMATION_DURATION 0.3
上述指令会把源代码中所有的ANIMATION_DURATION
替换为0.3,这可能就是我们想要的结果,不过这样定义出来的常量没有类型信息,加入这个宏被定义在某个头文件中,那么引入这个头文件的代码都会被替换。
有个方法比使用宏定义更好
static const NSTimeInterval kAnimationDuration = 0.3;
这种方式定义的常量包含类型信息,更清楚的描述了常量的定义,变量一定要同时用static const
来声明,如果试图修改const
修饰符所声明的变量,编译器就会报错,而使用static
符号则表示该变量仅在定义此变量的编译单元中可见。
如果我们需要定义一个全局可见的的常量,其定义的方式则有所不同,应如下定义:
//header file
extern NSString *const EOCString;
//implement file
NSString *const EOCString = @"VALUE";
const
的位置一定要注意,EOCString
是一个指针,指向一个NSString
对象,我们不希望有人修改它指向其他的NSString
对象。extern
会告诉编译器在全局符号表中有一个名为EOCString
的符号,编译器无需查看其定义就允许使用此常量,这类常量一定要定义,而且只能定义一次。由于是全局可使用,在命名时一定要注意,以免发生冲突。
第五条:用枚举表示状态、选项、状态码
由于Objective-C
基于C语言,所以C语言有的功能它都有,其中之一就是枚举类型,系统框架中频繁使用到此类型。
枚举只是一种常量命名方式,某个对象所经历的各种状态就可以定义为一个简单的枚举集:
enum ConnectionState{
ConnectionStateDisconnected,
ConnectionStateConnecting,
ConnectionStateConnected,
}
每种状态都用一个便于理解的值来表示,所以这样写出来的代码更易于读懂,编译器
会为枚举分配一个独有的编号,从0开始,每个枚举递增1,实现枚举所用的数据类型取决于编译器。
然而定义枚举变量的方式缺不太简洁,要依如下语法编写:
enum ConnectionState state = ConnectionStateDisconnected;
我们可以使用typedef
关键字重新定义枚举类型,来简化声明:
enum ConnectionState{
ConnectionStateDisconnected,
ConnectionStateConnecting,
ConnectionStateConnected,
};
typedef enum ConnectionState ConnectionState;
这样我们就可以用简写的ConnectionState来代替完整的enum ConnectionState了:
ConnectionState state = ConnectionStateDisconnected;
C++11修改了枚举的某些特性,可以指明用何种“底层数据类型”来保存枚举类型的变量。
enum ConnectionState:NSInteger{
ConnectionStateDisconnected = 1,
ConnectionStateConnecting,
ConnectionStateConnected,
}
上述代码把ConnectionStateDisconnected的值设为1,而不是编译器所分配的0.
还有一种情况应该使用枚举值,那就是定义选项的时候,若这些选型可以彼此组合,则更应该如此,只要枚举定义的对,各选项之间就可以通过按位或操作符来组合:
enum UIViewAutoresizing {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
用按位操作符即可判断出是否已经启用某选项:
enum UIViewAutoresizing resizing = UIViewAutoresizingFlexibleWidth| UIViewAutoresizingFlexibleHeight;
系统库中频繁使用这个办法。
Foundation
框架中定义了一些辅助的宏,用这些宏来定义枚举类型时,也可以指定用于保存枚举值的底层数据类型。
typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
UIViewAnimationTransitionNone,
UIViewAnimationTransitionFlipFromLeft,
UIViewAnimationTransitionFlipFromRight,
UIViewAnimationTransitionCurlUp,
UIViewAnimationTransitionCurlDown,
};
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
凡是需要以按位或操作来组合的枚举都应使用NS_OPTIONS
定义,若是枚举不需要互相组合,则应使用NS_ENUM
来定义。