Effective Objective-C 2.0 阅读笔记(一)

一:Objcetive-C的特性

Objective-C使用动态绑定的消息结构,也就是说,在运行时才会检查对象。对象所占内存总是分配在“堆空间”。

二:在类的头文件中尽量少引入其他头文件

why:
若现有A,B两类,A中有B类类对象,故通常在A类中导入B类头文件,虽可行但是不够优雅,有时我们并不需要知道B类的全部细节,故会增加编译时间,于是我们有了向前声明

solve:

@class B

A类的实现文件则需引入B类的头文件,因为若要使用后者,则必须知道其接口的所有细节。

同时向前声明也解决了相互引用的问题。若我们采用头文件导入,看看会怎样

A.h
#import <Foundation/Foundation.h>
#import"A.h"
#import"B.h"

@interface A : NSObject
@property(nonatomic,strong) B* b;
@end
B.h
#import <Foundation/Foundation.h>
#import"A.h"
#import"B.h"

@interface B : NSObject
- (void)add:(A*)a;
- (void)remove:(B*)b;

若这样编码,则会出现相互引用的情况,编译A时,需要知道B,而编译B时,则需要知道A,使用#import而非#include指令虽不会导致死循环,但却意味着两个类中有一个类无法被正确编译。
有时无法正常使用向前声明,比如某个类遵循一项协议。这种情况下,尽量把“该类遵循某协议”的这条声明移至“class-continuation分类”中。如果不行,就把协议单独放在一个头文件中,然后将其引入。

三:多使用字面量语法,少用与之等价的方法

what:什么是字面量语法?
字面字符串:
NSString *string = @"Effective Objective-c 2.0";

字面数值:
NSNumber *someNumber = @1;

字面量数组:
NSArray *animals = @[@"dog",@"cat",@"mouse"];
NSString *dog = animals[1];

字面量字典:
NSDictionary * dic = @{@"firstName":@"Matt",
					   @"lastName":@"Galloway"};

可变数组和字典:
//标准
[mutableArray replaceObjectAtIndex:1 withObject:@"dog"];
[mutableDictionary setObject:@"Galloway" forkey:@"lastname"];
//字面量
mutableArray[1] = @"dog";
mutableDictionary[@"lastName"] = @"Galloway";
why ?

使用字面量语法可缩减代码长度,使其更为易读。在数组和字典中,使用字面量语法更安全,若某一个value为nil,则会抛出异常,通过异常可以更快的发现这个错误。

局限性

字面量语法有个小小的限制,就是除了字符串以外,所创建出的对象必须属于Foundation框架才行。如果自定义了这些类的子类,则无法用字面量语法创建其对象。

要点
  • 应该使用字面量语法来创建字符串、数值、数组、字典
  • 应该通过取下标操作来访问数组下标或字典中的键所对应的元素
  • 用字面量语法创建数组和字典时,若值为nil,则会抛出异常。因此,务必确保值里不含nil

四:多用类型常量,少用#define预处理指令

常量的命名法是:若常量局限于某编译单元(实现文件)中,则在前面加字母k;若常量在类之外可见,则通常以类名为前缀。

若要开发一个动画的app,类中含有表示动画播放时间的常量,那么可以这样写

AnimatedView.h

import <UIKit/UIKit.h>

@interface AnimatedView : View
- (void) animate;
@end

AnimatedView.m

static const NSTimeInterval kAnimationDuration = 0.3;

@implementation AnimatedView
- (void)animate {
		
}

变量一定要同时使用staticconst来声明。如果试图修改由const修饰符所声明的变量,则会报错。而static修饰符则意味着该变量仅在定义此变量的编译单元中可见。假如不加static,则编译器会为它创建一个“外部符号”。若其他文件声明了同名变量,则会报错。

派发通知时,需要使用字符串来表示此项通知的名称,而这个名字就可以声明为一个外界可见得常值变量。此类变量需放在”全局符号表“中。

In the header file
extern NSString *const EOCStringConstant;

In the implementation file
NSString *const EOCStringConstant = @"VALUE";
  • 编译器看到头文件中的extern关键字,是告诉编译器,在全局符号表中将会有一个名叫EOCStringConstant的符号。
  • 由实现文件生成目标文件时,编译器会在”数据段(data section)“为字符串分配存储空间。链接器会把此目标文件与其他目标文件相链接,以生成最终的二进制文件。凡是用到EOCStringConstant这个全局符号的地方,链接器都能将其解析。
要点
  • 不要用预处理指令定义常量。
  • 在实现文件中使用staticconst来定义”只在编译单元内可见的常量“。
  • 在头文件中使用extern来声明全局变量,并在相关实现文件中定义其值。名称要加以区隔,通常用与之相关的类名做前缀

五:用枚举表示状态、选项、状态码

要点
  • 应该用枚举来表示状态机的状态、传递给方法的选项以及状态码等值,给这些值起个易懂得名字。
  • 如果把传递给某个方法的选项表示为枚举类型,而多个选项又可同时使用,那么就将各选项值定义为2的幂,以便通过按位或操作将其组合起来。
  • 用NS_ENUM与NS_OPTIONS宏来定义枚举类型,并指明其底层数据类型。
  • 在处理枚举类型的switch语句中不要实现default分支。

简单来说,若不需要组合,则用NS_ENUM,否则用NS_OPTIONS。

参考文章:
OC中的枚举(NS_ENUM和NS_OPTION)
多少人跪求的iOS NS_OPTIONS , 1 << 0 是个什么玩意儿?

六:理解”属性“这一概念

用Objective-C等面向对象语言编程时,”对象“(object)就是”基本构造单元“,开发者可通过对象来存储并传递数据。在对象之间传递数据并执行任务的过程就叫做”消息传递“

@dynamic关键字,它会告诉编译器:不要自动创建实现属性所用的实例变量,也不要为其创建存取方法。

属性特质:
使用属性时还有一个问题要注意,就是其各种特质(attribute)设定也会影响编译器所生成的存取方法。

要点:
  • 可以用@property语法来定义对象中所封装的数据。
  • 通过“特质”来指定存储数据所需的正确语义
  • 在设置属性所对应的实例变量时,一定要遵从该属性所声明的语义
  • 开发程序时应该使用nonatomic属性,因为atomic属性会严重影响性能

七:在对象内部尽量直接访问实例变量

在对象之外访问实例变量时,总是应该通过属性来做,然而在对象内部访问实例变量时又该如何呢?

举例:

@interface EOCPerson : NSObject
@property (nonatomic , copy) NSString * firstName;
@property (nonatomic , copy) NSString * lastName;

- (NSString*)fullName;
- (void)setFullName:(NSString*)fullName;
@end 

这两个“便捷方法”可这样实现

 - (NSString*)fullName{
	return [NSString stringWithFormant:"%@ %@",self.firstName,self.lastName];
	//_firstName  _lastName;
}

 - (void)setFullName:(NSString*)fullName{
	NSArray *components = [fullName componentsSeparatedByString:@""];
	self.firstName = [components objectAtIndex:0];
	self.lastName = [components objcetAtIndex:1];
	//_firstName _lastName
}

self.firstName 换做_firstName(self.lastName同样)
这两种写法有几个区别

  • 由于不经过Objective-C的“方法派发”步骤,所以直接访问实例变量(后者)的速度当然比较快。
  • 直接访问实例变量时,不会调用其“设置方法”,这就绕过了为相关属性所定义的“内存管理语义”。比如,若在ARC下直接访问了一个声明为copy的属性,那么并不会拷贝该属性,只会保留新值并释放旧值
  • 如果直接访问实例变量,那么不会触发“键值观测(KVO)”通知。
  • 通过属性来访问有助于排查与之相关的错误,因为可以给“获取方法”和“设置方法”中新增断点,监控该属性的调用者及访问时机。
要点
  • 在对象内部读取数据时,应该直接通过实例变量来读,而写入数据时,则应通过属性来写。
  • 在初始化方法及dealloc方法中,总是应该直接通过实例变量来读写数据
  • 有时会使用惰性初始化技术配置某份数据,这种情况下,需要通过属性来读取数据。

八:理解“对象等同性”这一概念

要点:
  • 若想检测对象的等同性,请提供“isEqual”与hash方法。
  • 相同的对象必须具有相同的哈希码,但是两个哈希码相同的对象却未必相同。
  • 不要盲目地逐个检测每条属性,而是应该依照具体需求来制定检测方案。
  • 编写hash方法时,应该使用计算速度快而且哈希码碰撞几率低的算法。

九:以“类族模式”隐藏实现细节

类族是一种很有用的模式,可以隐藏“抽象基类”背后的实现细节。
举例:假设有一个处理雇员的类,每个雇员都有“名字”和“薪水”两个属性,管理者可以命令其执行日常工作。但是,各种雇员的工作内容却不同。经理在带领雇员做项目时,无徐关心每个人如何完成其工作,仅需指示其开工即可。

typedef NS_ENUM(NSUInteger,EOCEmployeeType) {
	EOCEmployeeTypeDeveloper;
	EOCEmployeeTypeDesigner;
	EOCEmployeeTypeFinance;
}

@interface EOCEmployee : NSObject
@property (nonatomic,copy) NSString * name ;
@property NSUInteger salary;


+ (EOCEmployee*)employeeWithType:(EOCEmployeeType)type;
- (void)doADaysWork;

@end 

@implementation EOCEmployee

+ (EOCEmployee*)employeeWithType:(EOCEmployeeType)type{
	switch (type){
		case EOCEmployeeTypeDeveloper:
			return [EOCEmployeeDeveloper new];
			break;
		case EOCEmployeeTypeDesigner:
			return [EOCEmployeeTypeDesigner new];
			break;
		case EOCEmployeeTypeFinance:
			return [EOCEmployeeTypeFinance new];
			break;
}

- (void)doADaysWork{
	//
}

@end

每个“实体子类”都从基类继承而来。例如:

@interface EOCmployeeDeveloper : EOCEmployee
@end

@implementation EOCEmployeeDeveloper
- (void)doADaysWork {

}
@end
要点
  • 类族模式可以把实现细节隐藏在一套简单的公共接口后面
  • 系统框架中经常使用类族
  • 从类族的公共抽象类中继承子类时要当心,若有开发文档,则应先阅读
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值