《Effective Objective-C 2.0》第一章总结

  • Objective-C通过一套全新语法,在C语言基础上添加了面向对象特性。Objective-C的语法中频繁使用方括号,而且不吝于写出极长的方法名,这通常令许多人觉得此语言较为冗长。其实这样写出来的代码十分易读,只是C++或Java程序员不太能适应。
  • Objective-C语言学起来很快,但有很多微妙细节需注意,而且还有许多容易为人所忽视的特性。另一方面,有些开发者并未完全理解或是容易滥用某些特性,导致写出来的代码难于维护且不易调试。本章讲解基础知识,后续各章谈论语言及其相关框架中的各个特定话题。

1、了解Objective-C语言的起源

Objective- C与C++、Java等面向对象语言类似,不过很多方面有所差别。语法上该语言使用“消息结构”而非“函数调用”。Objective-C语言由Smalltalk演化而来,后者是消息型语言鼻祖。
消息与函数调用之间的区别:

//Messaging(Objevtive-C)
Object *obj = [Object new];
[obj performWith:parameter1 and:parameter2];

//Function calling(C++)
Object *obj = new Object;
obj->perform(parameter1, parameter2);

关键区别:
使用消息结构的语言,其运行所应执行的代码由运行环境来决定;而使用函数调用的语言,则由编译器决定。对于函数结构,如果范例代码的调用函数是多态的,那么就在运行时按照虚方法表来查出来到底执行哪个函数,而采用消息结构的语言,不管是否为多态总是在运行时才会去查找所要执行的方法。实际上,编译器甚至不关心接收的对象是何种类型。接受消息的对象问题也要在运行时处理,其过程叫做“动态绑定”。

运行期组件
Objective-C的重要工作都由“运行期组件”(runtime component)而非编译器来完成。使用Objective-C的面向对象特性所需的全部数据结构及函数都在运行期组件里面。举例来说,运行期组件中含有全部内存管理方法。运行期组件本质上就是一种与开发者所编代码相链接的“动态库”(dynamic library),其代码能把开发者编写的所有程序粘合起来。这样的话,只需更新运行期组件,即可提升应用程序性能。而那种许多工作都在“编译期”(compile time)完成的语言,若想获得类似的性能提升,则要重新编译应用程序代码。

内存模型
理解C语言的内存模型(memory model),这有助于理解Objective-C的内存模型及其“引用计数”(reference counting)机制的工作原理。若要理解内存模型,则需明白:Objective-C语言中的指针是用来指示对象的。想要声明一个变量,令其指代某个对象,可用如下语法:

NSString *someString = @"The string";

例如上述代码,声明了一个someString 的变量,此变量为指向NSString的指针。所有OC语言的对象都必须这样声明,因为对象所占内存总是分配在“堆空间”中,而绝不会分配在“栈”上。不能在栈中分配内存对象。

something变量指向分配在堆里的某块内存,其中含有一个NSString对象。也就是说,如果再创建一个变量,令其指向同一地址,那么并不拷贝该对象,只是这两个变量会同时指向此对象。

NSString *someString = @"The string";
NSString *anotherString = someString;

上述代码中:只有一个NSString实例,然而有两个变量只想此实例。两个变量都是NSString*型,这说明当前“栈帧”里分配了两块内存,这两块内存里的值都是NSSring实例的内存地址。
对象分配在栈上,而实例分配在堆中。
分配在堆中的内存必须直接管理,而分配在栈上用于保存变量的内存则会在其栈帧弹出时自动清理。

引用计数
OC将堆内存管理抽象出来了,不需要用malloc和free来分配或释放内存对象所占内存。OC运行期环境把这部分工作抽象为一套内存管理架构,名叫“引用计数”。

重点:

1.Objective-C为C语言添加了面向对象特性,是其超集。Objective-C使用动态绑定的消息结构,也就是说,在运行时才会检查对象类型。接收一条消息之后,究竟应执行何种代码,由运行期环境而非编译器来决定。
2.理解C语言的核心概念有助于写好Objective-C程序。尤其是要掌握内存模型和指针。


2、在类的头文件中尽量少引入其他头文件

在编译一个使用了EOCPerson类文件时,不需要知道EOCEmployer类的全部细节,只需要知道有一个类名叫EOCEmployer就好。因此可以在头文件中“向前声明”该类。

@class EOCEmployer;

在实现文件中需要引入EOCEmployer类的头文件,因为若要使用后者,则必须知道其所有接口细节。代码如下:

#import "EOCPerson.h"
#import "EOCEmployer.h"

@implementation EOCPerson

@end

将引入头文件的时间尽量延后,只有在确有需要时才引入,这样就可以减少类的使用者所需引入的头文件数量,因为一旦引入就会引入该头文件的所有内容,若此过程持续下去则会引入许多根本用不到的内容,增加编译时间。

向前引用

1.解决了这两个类相互引用问题
相互引用:有两个类,它们都在头文件中引入了对方的头文件,两个类都进行各自的引用解析,这样就会导致“循环引用”(chicken-and-egg situation)。虽然我们使用#import而非#include不会导致死循环,但是这意味着两个类中有一个类无法被正确编译。

2.但是,有时候就必须引入头文件,比如继承以及遵循的协议


3、多用字面量语法,少用与之等价的方法

编写OC程序时,总会用到某几个类,它们属于Foundation框架。虽让从技术上来说,不用Foundation框架也可使写出OC代码,但实际上却要经常使用到此框架。这几个类是NSString、NSNumber、NSArray、NSDictionary。从类名上即可看出各自所表示的数据结构。

  • 可以用“字符串字面量”创建NSString对象,同理也可以用这种字面量语法来创建NSNumber、NSArray、NSDictionary对象,从而缩减源代码长度使其更易读。
NSString* someString = @"Effective Objective"
  • 字面数值

有时需要把整数、浮点数、布尔值封入OC对象中。这种情况下可以用NSNumber类。

不使用字面量:

NSNumber *somenumber = [NSNumber numberWithInt:1];

使用字面量:

NSNumber *number = @1;

优点:使对象变得更整洁

  • 字面量数组

创建数组时不使用字面量语法:

NSArray *array = [NSArray arrayWithObjects:@"1", @"2", @"3", @"4", nil];

使用字面量:

NSArray *array = @[@"1", @"2", @"3", @"4"];

用字面量在取某个下标所对应的对象时更为容易。

对于字面量的话,需要注意的是,如果字面量初始化的有nil,就会报错,并且还需要注意,字面量初始化默认的是不可变的,如果需要初始化可变类型容器,就不能使用字面量进行初始化。

  • 字面量字典

“字典”是一种映射型数据结构,可向其中添加键值对。

不使用字面量语法:

NSDitionary* personData = [NSDictionarydictionaryWithObjectivesAndKeys:@"Matt",@"firstName",@"Galloway",@"lastName",[NSNumber numberWithInt:28],@"age",nil];

使用字面量语法:

NSDictionay* personData = @{@"firstName":@"Matt",@"lastName":,@"Galloway",@"age":@28};

优点:字典中的对象和键必须都是OC对象,所以不能把整数28直接放进去而要封装在NSNumber实例中才行。使用字面量语法只需在数字前加一个@字符即可。

字典按照键访问值:

NSString* lastName = personData[@"lastName"];

如果数组和字典对象是可变的,也可通过下标来修改其元素值。

4、多用类型常量,少用#define预处理指令

在定义常量时,比预处理更好的办法是用stactic const

定义常量的位置很重要。预处理指令的常量名不该使用在头文件中,因为所用引入了这个头文件的其他文件都会出现这个名字,其实就连用static const定义的常量也不应该出现在头文件中,因为OC没有“名称空间”这一概念,这样做相当于声明了一个全局变量。

const声明的变量不可变,static修饰符意味着该变量仅在定义此变量的编译单元中可见。编译器每收到一个编译单元(指每个类的实现文件),就会输出一份“目标文件”。

想定义一个全局常量的做法
定义使用extern关键字

在头文件

extern NSString *const string;

在实现文件

NSString *const string = @"hhhh";

要点总结

  • 不要用预处理指令定义常量。这样定义出来的常量不含类型信息,编译器只是会在编译前据此执行查找与替换操作。即使有人重新定义了常量值,编译器也不会产生警告信息,这将导致应用程序中的常量值不一致。
  • 在实现文件中使用static const来定义“只在编译单元内可见的常量”(translation-unit-specific constant)。由于此类常量不在全局符号表中,所以无须为其名称加前缀。
  • 在头文件中使用extern来声明全局常量,并在相关实现文件中定义其值。这种常量要出现在全局符号表中,所以其名称应加以区隔,通常用与之相关的类名做前缀。

5、用枚举表示状态、选项、状态码。

  1. 枚举enum只是一种常量命名方式,枚举只是一种常量命名方式,某个对象所经历的各个状态、定义选项或者把逻辑含义相似的一组状态码都可以放入一个枚举集里。
  2. 编译器会为枚举分配独有的编号,从0开始,每个枚举递增1。也可以手动设置某个枚举成员对应的值,后面的枚举值一次加1。实现枚举所用的数据类型取决于编译器,不过其二进制位(bit)的个数必须能完全表示下枚举编号才行(例如,最大编号是2,所以使用一个字节的char类型即可)
  3. 可以指明枚举用的何种底层数据类型,这样编译器清楚底层数据类型的大小,可以向前声明枚举类型。

要点总结

  • 应该用枚举来表示状态机的状态、传递给方法的选项以及状态码等值,给这些值起个易懂的名字。
  • 如果把传递给某个方法的选项表示为枚举类型,而多个选项又可同时使用,那么就将各选项值定义为2的幂,以便通过按位或操作将其组合起来。
  • 用NS_ENUM与NS_OPTIONS宏来定义枚举类型,并指明其底层数据类型。这样做可以确保枚举是用开发者所选的底层数据类型实现出来的,而不会采用编译器所选的类型。
  • 在处理枚举类型的switch语句中不要实现default分支。这样的话,加入新枚举之后,编译器就会提示开发者:switch 语句并未处理所有枚举。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值