一: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 {
}
变量一定要同时使用static
与const
来声明。如果试图修改由const
修饰符所声明的变量,则会报错。而static
修饰符则意味着该变量仅在定义此变量的编译单元中可见。假如不加static
,则编译器会为它创建一个“外部符号”
。若其他文件声明了同名变量,则会报错。
派发通知时,需要使用字符串来表示此项通知的名称,而这个名字就可以声明为一个外界可见得常值变量。此类变量需放在”全局符号表
“中。
In the header file
extern NSString *const EOCStringConstant;
In the implementation file
NSString *const EOCStringConstant = @"VALUE";
- 编译器看到头文件中的
extern
关键字,是告诉编译器,在全局符号表中将会有一个名叫EOCStringConstant
的符号。 - 由实现文件生成目标文件时,编译器会在”
数据段(data section)
“为字符串分配存储空间。链接器会把此目标文件与其他目标文件相链接,以生成最终的二进制文件。凡是用到EOCStringConstant
这个全局符号的地方,链接器都能将其解析。
要点
- 不要用预处理指令定义常量。
- 在实现文件中使用
static
、const
来定义”只在编译单元内可见的常量
“。 - 在头文件中使用
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
要点
- 类族模式可以把实现细节隐藏在一套简单的公共接口后面
- 系统框架中经常使用类族
- 从类族的公共抽象类中继承子类时要当心,若有开发文档,则应先阅读