Effective Objective-C 第一章阅读笔记
文章目录
- [Effective Objective-C 第一章阅读笔记](#Effective Objective-C 第一章阅读笔记)
OC的语言起源
OC采用的使用消息结构,而不是函数调用。OC由Smalltalk演化而来,后者是消息型语言的鼻祖。
消息与函数调用之间的区别看上去就像这样:
objc
// Messaging(Objective-C)
Object* obj = [Object new];
[obj performWith: parameter1 and: parameter2];
// Function calling(C++)
Object* obj = new Object;
obj->perform(parameter1, parameter2);
这里的关键区别在于,消息结构的语言,其运行时所应执行的代码由运行环境来决定;使用函数调用的语言,则由编译器来决定
如果这里面调用的函数时多态的,那么对于函数型语言则是通过一个虚方法表来查出应该执行哪一个函数,至于采用消息结构的语言,无论是否多态,总是在运行时才会去查找所要执行的一个方法。
OC的重要工作都由运行期组件完成,运行期组件本质上是一种与开发者所连接的动态库,这里会在后面的内容中介绍。
内存管理
OC的有些语法是照搬C语言的,OC中的指针式用来指示对象的。
objc
NSString *someString = @"The String"
上面代码中的someString变量是用来指代一个NSString的类型的,这里所有的对象都是存储到我们的堆空间中的,而不是栈区。也就是说这种代码是不可以存在的:
objc
NSString someString;
下面的代码体现出了一个浅拷贝的内容。
objc
NSString* someString = @"The string";
NSString* anotherString = someString;
下图描述了他的一个内存分布
分配在堆中的内存必须直接管理,分配在栈上的用于保存变量的内存会在其栈帧弹出时自动清理
OC采用引用计数来管理堆区的内存,当然OC中也存在定义中不含有*的变量
创建结构体相比,创建对象还需要额外开销,这里采用结构体可以节约很多内存。
小结
- Objective-C为C语言添加了面向对象特性,是其超集。Objective-C使用动态绑定的消息结构,也就是说,在运行时才会检查对象类型。接收一条消息之后,究竟应执行何种代码,由运行期环境而非编译器来决定。
在类的头文件中尽量少引入其他头文件
在开发中我们经常会创建很多类,在OC中类的头文件是如下的结构:
objc
// EOCPerson.h
#import <Foundation/Foundation.h>
@interface EOCPerson : NSObject
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@end
// EOCPerson.m
#import "EOCPerson.h"
@implementation EOCPerson
// Implementation of methods
@end
但一般我们可能会创建另一个类别EOCEmployer,后面发现每一个EOCEmployer都应该被EOCPerson持有,于是我们会在EOCPerson的头文件的前面添加一天#improt "EOCEmployer.h"
,尽管这个可以实现,但是浪费很多时间,在头文件部分我们其实只需要让EOCPerson知道这个类的存在就可以了,所以我们只用采用@class EOCEmployer
就可以实现了
如果在EOCPerson的代码中我们需要用到EOCEmployer的接口细节的时候,我们在类的实现部分在导入对应的头文件#improt "EOCEmployer.h
objc
// EOCPerson.m
#import "EOCPerson.h"
#import "EOCEmployer.h"
@implementation EOCPerson
// Implementation of methods
@end
同时向前声明还可以解决我们这里的两个类互相引用的一个问题
这里可以看到我们在两个头文件中互相引用的时候,会出现编译错误的问题,但是如果我们换成@class
就不会出现对应的一个问题
这里引用原文来介绍一下:
但是我们在引入协议的时候,就不可以使用向前声明。向前声明只能告诉编译器有某个协议,而编译器却要知道他定义的方法。
但是在某些委托协议中,就不用单独写一个头文件了。在那种情况下,协议只有与接受协议委托的类在一起定义才有意义,可以把那段实现代码放在对应分类中。
小结
- 除非确有必要,否则不要引入头文件,尽量采用向前声明来提及别的类
- 无法使用向前声明的时候,尽量把'这个类遵循某协议'这一条声明放在分类中,
多用字面量语法
这部分内容主要还是一些更加便捷的语法:
比方说NSNumber可以采用
objc
NSNumber* number = @1
NSNumber* number = @1.5f
NSNumber* number = @YES
NSNumber* number = @'a'
int x = 3
float y = 6.32f
NSNumber* = @(x * y)
NSArray可以采用
objc
NSArray* ary = @[@"123", @"223", @"3214"];
//取对应的一个下标
ary[1]//字面量取法
NSDictionary可以采用
objc
NSDictionary* data = [@"123":@"213"];
//取某一个值
data[@"123"];
可变数组与字典
objc
mutableAry[1] = @"dog"
mutableDryp[@"213"] = @"23144"
- 注意使用字面量语法的时候,我们要保证我们的值不会出现nil,如果出现nil会抛出异常。
多用类型变量,少用#define预处理指令
熟悉C语言的我们,对于下面这一行代码也是比较熟悉,但是这段代码其实会造成某些隐患
假设这个指令声明在某个头文件中,那么所有引入了头文件的代码,他都会被替换。
当我们在完成一个很大的项目中,很可能会出现重复的常量名,这样可能会出现一个替换,从而导致代码出现一些问题。
我们应该把预处理指令换成我们的一个类型常量:
objc
static const int kDuration = 30;
我们可以发现他有类型信息,可以更好的描述来常量的含义,这里我们命名常量的时候,如果局限在某一个编译单元中就加字母k,如果常量在类之外可见,则通常以类名为前缀。
然后我们尽量不要在头文件中定义,因为我们如果在头文件中声明的话,就相当于声明了一个名字为kDuration的全局变量,所以我们不应该在头文件中定义。
只要不打算公开这个常量,我们应该将其定义在该常量的实现文件中。变量一定要同时用static和const来声明。
- 采用static可以保证这个变量只能在定义此变量的编译单元中可见(也就是定义他的一个实现的.m文件)
- 采用const可以保证他不被修改。
如果不采用static,这样如果在另一个实现文件中定义一个同名的变量会抛出一个错误。
这里其实采用static和const一起来修饰的话,编译器其实还是和#define一样的实现,但是他会有类型信息。
在某些情况下,我们可能需要给外部一个可见的常量,这类常量我们应该这样定义:
objc
extern NSString* *const EOCString;
extern const int duration;
//头文件
NSString *const EOCString = @"Value";
const int duration = 30;
//实现部分
这里我们要注意const修饰的位置,const是为了保证我们的这个指向EOCString的指针不变。
编译器看到我们在头文件中的extern就会把他添加到全局符号表中,因为符号会被添加到全局符号表中,所以命名常量需谨慎,千万不能出现名称冲突。
总之,我们呢不要使用预处理定义常量,应该借助编译器来保证我们呢的常量正确,在实现部分可以采用static和const。(这里笔者之后会写一篇有关这几个关键字的博客)
小结
- 不要用预处理命令
- 在实现文件中用static和const来定义在编译单元内可见的常量
- 在头文件中用extern来声明全局变量,并在相关实现文件中定义他的值
枚举表示状态,选项,状态码
这里简单介绍一下他的语法。
objc
enum EOCConnectionState {
EOCConnectionStateDisconnected
};
typedef enum EOCConnectionState EOCConnectionState;
EOCConnectionState state = EOCConnectionStateDisconnected;
在C++11之后我们可以指定使用何种底层数据类型。
objc
typedef enum : NSUInteger { //这里保证我们的一个底层数据类型是NSInteger
<MyEnumValueA>,
<MyEnumValueB>,
<MyEnumValueC>,
} <MyEnum>;
我们也可以在某一个值开始,通过手工来指定某个枚举成员所对应的一个值。
objc
enum EOCConnectionState {
EOCConnectionStateDisconnected = 1,
EOCConnectionStateConnected
};
这里就可以通过按位与来组合不同的选项。
最后我们在switch语句中可以用枚举值来代替对应的一个选择。
然后我们的switch中尽量不要使用default的分支。