小蓝书第一章学习
Objective-C的起源
Objective-C是由Smaltalk语言演变而来的,Smalltalk是一种消息类的语言,故而OC也是一种消息类语言。
消息与函数点调用的区别
//Message (Objective-C)
Object* obj = [Object new];
[obj performWith:parameter1 and: parameter2];
//Function Calling (C++)
Object* obj = new Object;
obj->perform(parameter1, parameter2);
区别:使用消息类语言时,它运行时所执行的代码是由运行环境来决定的。使用面向对象的语言,使用函数调用的语言,它运行时所执行的代码则是由编译器来决定。
Objective-C是C语言的超集,故而C语言的所有功能在OC中都可以使用。所以同时掌握OC和C语言两种语言的核心概念,才能写出高效的代码。尤其重要的是理解C语言的内存模型,有助于理解OC的内存模型和“引用计数”机制的工作原理。
OC的内存管理
OC中的指针是用来指示对象的,想要声明一个变量,必须要指向对象。所以OC中对象所占的内存总是分配在“堆空间“,而非“栈“上。(变量储存在栈上,对象储存在堆上)
NSString* str = @"hello, world";
Objective-C将堆内存管理抽象出来,不需要用malloc或free来分配或释放所占用的内存,而是将它们抽象成一套内存管理结构,名为引用计数。
要点
Objective-C为C语言添加了面向对象特性,是其超集。Objective-C使用动态绑定的消息结构,也就是说,在运行时才会检查对象类型。接收一条消息之后,究竟应执行何种代码,由运行期环境而非编译器来决定。- 理解C语言的核心概念有助于写好
Objective-C程序。尤其是要掌握内存模型和指针。
在类的头文件中尽量少引入其他头文件
OC中引入头文件的标准方式
OC中也使用头文件和实现文件来区隔代码。使用OC来编写“类“的标准方式:以类名做文件名,分别创建两个文件,分别为头文件.h和实现文件.m
//头文件
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@end
//实现文件
#import "ViewController.h"
@interface ViewController ()
@end
向一个类中引入另一个类
当在OC中我们向一个类中引入另一个类的方法通常是向这个类的头文件中一并引入另一个类的头文件
#import <Foundation/Foundation.h>
#import "EOCEmployer.h"
@interface EOCPerson: NSObject
@property (nonatomic, strong) ECOEmployer* employer;
@end
但是这种方式不够优雅,在编译一个使用ECOPerson类的文件时,不需要知道ECOEmployer这个类的全部细节,所以我们可以使用@Class ECOEmployer;这种方式来告诉编译器。这叫做“向前声明“该类。
而在实现文件中,我们需要引入ECOEmployer.h,这则是因为我们使用时,需要知道这个类的全部接口细节。
#import "EOCEmployer.h"
#import "ECOEmployer.h"
@implementtion ECOPerson
@end
将引用头文件的时机延,只有在需要使用时才引入,这样可以减少类使用者所需引入的头文件的数量,减少第一中方式所存在的编译时间较长的问题。
向前引用的好处
向前引用解决了两个类循环引用的问题。当两个类在头文件中都引入对方的头文件,那么就会导致“循环引用”,虽然使用#import而非#include,不会导致死循环,但是还是会导致一个类不能正确编译。但是有时候必须要引入头文件,例如遵守或继承协议时。
要点
- 除非确有必要,否则不要引入头文件。一般来说,应在某个类的头文件中使用向前声明来提及别的类,并在实现文件中引入那些类的头文件。这样做可以尽量降低类之间的耦合(coupling)。
- 有时无法使用向前声明,比如要声明某个类遵循一项协议。这种情况下,尽量把“该类遵循的协议”的这条声明移至“class-continuation分类”中。如果不行的话,就把协议单独放在一个头文件中,然后将其引入
多用字面量语法,少用与之等价的方法
在编写OC程序时,总会用到某几个类,他们属于Foundation框架,但从技术上说,不用Foundation框架也可以写出OC代码,这几个类就是NSString、NSArray、NSDictionary、NSNumber从类名上就可以看出他们的作用。
NSString* str = @"hello, world";
这就是一个简单的字面量字符串,在我们编写程序中,使用字面量语法可以缩短代码长度,使其更加易读。
字面数值
有时候我们需要将布尔值、整数、浮点数封装入OC对象中,这时我们就要使用NSNumber这个类。如果不使用字面量语法,我们则需要使用:
NSNumber* someNumber = [NSNumber numberWithInt:1];
而如果我们使用字面量语法,只需要NSNumber* someNumber = @1;即可。
下面举例说明:
NSNumber* boolNumber = @YES;
NSNumber* floatNumber = @3.1f;
NSNumber* doubleNumber = @3.1415;
NSNumber* charNumber = @'a';
NSNumber* intNumber = @3;
这样可以让NSNumber对象变得十分整洁,没有多余的语法成分。
字面量数组
数组是我们经常使用的一个数据结构,如果不使用字面量语法,创建数组就是:
NSArray& arr = [NSArray arrayWithObjects:@"cats", @"dogs", @"pigs", nil];
如果使用字面量语法,就是:
NSArray* arr = @[@"cats", @"dogs", @"pigs"];
使用字面量语法,不仅创建简单,而且便于操作数组,如果不使用字面量语法,想取出下标为1的元素的方法是NSString* str = [arr objectWithIndex:1];,反之,取出下标的方法为NSString* dogs = arr[1];。
我们要注意,在使用字面量语法时,数组中元素不能为nil,否则会抛出异常,这是由于字面量语法实际上是一种“语法糖“,本质上来说,就是先创建一个数组,再把方括号中所有对象添加到这个数组中来。当然,这样一来,当数组中存在一个元素为nil,就会停下运行程序,而使用字面量语法可以很快的发现这个问题。
字面量字典
字典创建方式如下:
NSDictionary *personData = [NSDictionary dictionaryWithObjectivesAndKeys:@"Mett", @"firstName", @"Galloway", @"lastName", [NSNumber numberWithInt:28], @"age", nil];//他的顺序是<对象>,<键值>,<对象>,<键值>
使用字面量语法时:
NSDictionary *personData = @{@"firstName": @"Matt", @"lastName": @"Galloway", @"age": @28};
我们需要注意的是,字典中的对象必须是OC对象,所以不能将整数等直接放入,需要先使用NSNumber对象封装。
同样,与上面的数组一样,使用字面量语法创建时,遇到nil,会抛出异常。
可变数组和字典
可变数组和字典使用时,当我们要取某一个元素进行修改时,使用字面量语法十分简便:
mutableArray[1] = @"dog";
mutableDictionary[@"lastName"] = @"Galloway";
局限性
字面量语法有个小的限制,就是除了字符串以外,所创建出来的对象必须属于Foundation框架才可以,如果自定义了这些类的子类,就不能使用字面量语法。
要点
- 应该使用字面量语法来创建字符串、数值、字典。与创建此类对象的常规方法相比,这么做更加简明扼要。
- 应该通过取下标操作来访问数组下标或者字典中的键对应的元素。
- 用字面量语法创建数组或者字典时,若值中有nil,则会抛出异常。因此,务必确保值里不含nil。
多用类型常量,少用#define预处理命令
当我们在编写代码时,将某一个时间提取为常量,如果使用#define来替换的话,在所有引入该头文件的文件中,都会被替换成这个时间,这样反而破坏了程序。这时,我们可以使用下面这个方法:
static const NSTimeInterval KAnimationDuration = 0.3;
在我们对变量命名时,常用的命名法是:若常量局限于某个“编译单元”(即实现文件中),我们在开头加上k,若是常量之外的类可见,则通常以类名为前缀。
定义常量的位置很重要,我们不应该定义在头文件中,这样做相当于声明了一个全局变量,如果不打算公开某个常量,我们则应该将其定义在使用该常量的实现文件中。
使用static const而不是用#define的原因:
变量一定要同时使用static和const来声明,如果试图修改由const修饰符所声明的变量,那么编译器就会报错。实际上,当我们同时使用static和const声明时,编译器根本不会创建符号,而是向#define预处理命令一样,将所有遇到的变量都替换为常值。
要点
- 不能用预处理指令定义常量。这样定义出来的常量不含类型信息,编译器只是会在编译前据此执行查找与替换操作。即使有人重新定义了常量值,编译器也不会产生警告信息,这将导致应用程序中的常量值不一致。
- 在实现文件中使用static const来定义“只在编译单元内可见的常量”。由于此常量不在全局符号表中,所以无需为其名称加前缀。
- 在头文件中使用extern来声明全局变量,并在相关实现文件中定义其值。这种常量要出现在全局符号表中,所以其名称应该加以区隔,通常用与之相关的类名做前缀。
用枚举表示状态、选项、状态码
在OC中我们经常使用枚举来表达按钮的状态等,下面介绍一下枚举的几种使用
单项枚举
enum EOCConnectionState {
EOCConnectionStateDisconnected;
EOCConnectionStateConnecting;
EOCConenctionStateConnected;
}
由于每种状态都用一个比较好理解的值来表示,所以写出的代码更加简洁易读。编译器会为枚举分配一个独有的编号,从0开始,每个枚举值加1。
但是我们定义枚举变量的方式不太简洁,我们可以使用
typedef关键字来重新定义枚举类型。
typedef enum EOCConnectionState EOCConectionState,这样一来,我们就可以用EOCConectionState来代替之前的enum EOCConnectionState了。
我们也可以将EOCConectionDisconnected设置成1,这样后面两个都会在前一个的基础上加1,,这样一来,EOCConnectionConnected的值就是3了。
enum EOCConnectionState {
EOCConnectionStateDisconnected = 1;
EOCConnectionStateConnecting;
EOCConenctionStateConnected;
}
多项枚举
在OC中还有一种情况是在定义选项时,这些选项可以彼此组合。只要枚举定义的对,各选项可以通过**“按位或操作符”**来组合。下面通过UI框架中的一个枚举类型来说明。
enum UIViewAutoresizing {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5,
};
在每个枚举值所表示的二进制中,只有1个二进制表示为1。用按位或操作符可以组合出多个选项。

与switch语句结合
typedef NS_ENUM (NSUInteger, EOCConnectionState) {
EOCConnectionStateDisConnected,
EOCConnectionStateConnecting,
EOCConnectionStateConnected,
};
switch (_currentState) {
EOCConnectionStateDisConnected:
//Handle disconnected state
break;
EOCConnectionStateConnecting:
//Handle connecting state
break;
EOCConnectionStateConnected:
//Handle connected state
break;
}
在使用枚举来定义状态机,最好不要使用default分支。如果后面在添加一种状态,编译器会报警告,提示新加入的状态并没有在swtich语句中处理,如果加入default分支,就不会报这个警告,,它就会处理这个分支。
要点
- 应该用枚举来表示状态机的状态、传递给方法的选项以及状态码等值,给这些值起个易懂的名字。
- 如果把传递给某个方法的选项表示为枚举类型,而多个选项又可同时使用,那么就将各选项值定义为2的幂,以便通过按位或操作将其组合起来。
- 用NS_ENUM与NS_OPTIONS宏来定义枚举类型,并指明其底层数据类型。这样做可以确保枚举是用开发者所选的底层数据类型实现出来的,而不会采用编译器所选的类型。
- 在处理枚举类型的switch语句中不要实现default分支。这样的话,加入新枚举之后,编译器就会提示开发者:switch语句并未处理所有枚举。
2304

被折叠的 条评论
为什么被折叠?



