《Effective Objective-C 2.0编写高质量i
1. 了解OC语言的起源
知识点
- OC: 消息型类型,使用消息结构,其运行时所应执行的代码由 运行环境 决定;
- 而使用函数调用的语言,则由 编译器 决定。
- 对象所占内存总是分配在 堆空间,而绝不会在栈上。
- 栈 上保存的是指向对象的指针。
- 定义里不含 “ * ” 的变量(非对象类型),可能会被分配到 栈 上,如CGRect。它是C 结构体。
要点总结
-
Objective-C 为 C 语言添加了 面向对象 的特性,是其超集。Objective-C使用 动态绑定的消息结构 。所以,在运行时 才会检查对象类型。接收一条消息之后,究竟应执行何种代码,由运行时环境决定,而非编译器。
-
理解C语言的核心概念(重点是内存模型、指针)有助于写好OC程序。
2. 在类的头文件中尽量少引入其他头文件
想要在一个文件中引入另一个(如Model:Person.h)类,且只需要知道类名,可以:
//#import "Person.h"
@class Person;
这种写法,叫“向前声明”该类。总结:
除非有必要,否则不要引入头文件。一般来说,应在某个类的 (xxxx.h)头文件 中使用 向前声明 来提及别的类,并在 (xxxx.m)实现文件 中引入那些类的头文件。这样做 可以尽量降低类之间的耦合(coupling)。
有时无法使用向前声明,如声明某个类遵循一项协议。这种情况下,尽量把 “该类遵循某协议” 的这条声明移至 “class-continuation分类” 中。如果不行的话,就把协议单独放在一个头文件中,然后将其引入。见27条
3. 多用字面量语法,少用与之等价的方法
知识点
- 使用字面量语法(Literal syntax)可以缩短源代码长度,使之更为易读。如
- 字面数值
/** 1 */
NSString *someString1 = [[NSString alloc] initWithString:@"I'm wanazen"];
//可简写为:
NSString *someString2 = @"I'm wanazen";
/** 2 */
NSNumber *someNumber = [NSNumber numberWithInt:1];
//可简写为:
NSNumber *intNumber = @1;
NSNumber *doubleNumber = @3.14159;
NSNumber *boolNumber = @YES;
NSNumber *charNumber = @'a';
int x = 5;
float y = 8.99f
NSNumber *expressionNumber = @(x * y) ;
- 字面量数组
/** 创建 */
NSArray *animals = [NSArray arrayWithObjects:@"cat", @"dog", @"mouse", nil];
//可简写成
NSArray *animalsNew = @[@"cat", @"dog", @"mouse"];
/** 取值 */
NSString *cat = [animals objectAtIndex:0];
//可简写为
NSString *catNew = animalsNew[i];
- 字面量字典
/** 创建 */
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:@"Wanazen",@"firstName", @"Lona", @"lastName", [NSNumber numberWithInt:2019], @"year"];
//可简写为
NSDictionary *dic2 = @[@"firstName":@"Wanazen", @"lastName":@"Lona", @"year":@2019];
/** 取值 */
NSString *firstName = [dic objectForKey:@"firstName"];
//可简写为
NSString *firstName2 = dic[@"firstName"];
//赋值操作可以写成字面量。
- 字面量语法实际上只是一种“语法糖”(syntax sugar),其效果等于是 先创建了一个对象,然后执行了那些简写之前的操作。
- 数组、字典等一旦有值为nil时,可能会抛出异常。
- 限制:除了字符串之外,所创建出来的对象必须属于Foundation框架才行。
- 使用字面量创建出来的字符串、数组、字典对象都是不可变的(immutable)。若想创建可变版本的对象,则需要复制一份。
//虽然会多调用一个方法,还要在创建一个对象,不过优大于劣
NSMutableArray *mutableArray = [[@1, @2, @3, @4] mutableCopy];
要点总结:
-
应该 使用字面量语法来创建字符串、数值、数组、字典。与创建此类对象的常规方法相比,这么做更加简明扼要,易读。
-
应该通过 取下标操作 来访问数组下标或字典中的键所对应的元素。
-
用字面量语法创建数组或字典时,若值中有nil,则会抛出异常。因此,务必确保值里不含nil。
4. 多用类型常量,少用#define预处理指令
优化前的目标代码:
#define ANIMATION_DURATION 0.3
- 只在文件内(.m)使用 的写法:
/*
升级的写法.
根据实际数据类型声明常量。
const:不允许修改。修改后编译器报错
static:局限在编译单元.m文件中
*/
static const NSTimInterval kAnimationDuration = 0.3;
- 在被引入的文件同样生效(对外开放) 的写法:
//在.h文件 - 声明
// LoginManager.h
#import <Foundation/Foundation.h>
extern NSString *const LoginManagerDidLoginNotification;
@interface LoginManager()
***
@end
//在.m文件 - 定义
// LoginManager.m
#import "LoginManager.h"
NSString *const LoginManagerDidLoginNotification = @"LoginManagerDidLoginNotification";
@implementation LoginManager
***
@end
常用的命名法:
- 若常量局限在.m文件中,以 “k” 开头;
- 在类之外可见,常以 类名 开头。
要点总结:
-
不要使用预处理指令(#define)定义 常量 。这样定义出来的常量不含类型信息,编译器只是会在编译前据此执行查找和替换操作。即使有人重新定义了常量值,编译器也不会产生警告信息,这将导致应用程序中的常量值不一致。
-
在实现文件(.m)中使用 static const 来定义 “只在编译单元内可见的常量”。由于此类常量不在全局符号表中,所以无需为其名称加前缀。
-
在头文件中使用 extern 来声明全局常量,并在相关实现文件中定义其值。这种常量药出现在全局符号表中,所以其名称应加以区隔,通常用与之相关的类名做前缀。
5. 用枚举表示状态、选项、状态码
由于 OC 基于 C 语言,所以 C 语言有的功能它都有。枚举(ENUM),是一种常量的命名方式。
/**
C语言下的语法
*/
enum EOCConnectionStateConnectionState {
EOCConnectionStateDisConnected = 1,
EOCConnectionStateConnecting,
EOCConnectionStateConnected,
};
/**
OC中
*/
typedef NS_ENUM(NSUInteger, EOCConnectionState) {
EOCConnectionStateDisConnected,
EOCConnectionStateConnecting,
EOCConnectionStateConnected,
};
//适用于 按位或操作来组合 的枚举
typedef NS_OPTIONS(NSUInteger, EOCPermittedDirection) {
EOCPermittedDirectionUp = 1 << 0,
EOCPermittedDirectionDown = 1 << 1,
EOCPermittedDirectionLeft = 1 << 2,
EOCPermittedDirectionRight = 1 << 3,
};
要点总结:
-
应该用 枚举 表示状态机的状态、传递给方法的选项以及状态码等值,给这些值起简单易懂的名字。
-
如果把传递给 某个方法的选项 表示为枚举类型,而多个选项又可同时使用,那么就将各选项定义为2的幂对应的值,便于按 位 或 操作组合使用。
-
用 NS_ENUM 与 NS_OPTIONS 宏 来定义枚举类型,并指明其底层数据类型。可以确保枚举是用开发者所选的底层数据类型实现的,而不会采用编译器所选的类型。
-
在处理枚举类型的switch语句中不要出现 default分支 。这样在加入新枚举值之后,编译器会提示开发者:在switch语句中并未处理所有枚举。