第 1 章 熟悉Objective-C
第 1 条: 了解Objective-C语言的起源
- Objective-C语言使用"消息结构"而非"函数调用",使用消息结构的语言,其运行是所应执行的代码是由运行环境来决定的;而使用函数调用的语言,则由编译器决定
- 采用消息结构的语言不论是否多态,总是在运行时才会去查找所要执行的方法
- Objective-C的重要工作都由"运行期组件"(runtime component)而非编译器来完成.使用Objective-C的面向对象特性所需的全部数据结构都在运行期组件里面
- Objective-C是C的"超集"(superset),所以C语言中的所有功能在编写Objective-C代码时依然适用
- 在Objective-C代码中,定义里不含*的变量,它们可能会使用"栈空间"(stack space),这些变量所保存的不是Objective-C对象
第 2 条: 在类的头文件中尽量少引入其他头文件
在创建的一个类中,需要引入一个新类,来作为其的一个属性,我们不应该引入这个新类的头文件,而是使用"向前声明"(forward declaring)使用class来处理,然后在实现文件中引入新类的头文件
//EOCPerson.h
#import <Foundation/Foundation.h>
@class EOCEmployer
@interface EOCPerson : NSObject
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic, strong) EOCEmployer *employer;
@end
//EOCPerson.m
#import "EOCPerson.h"
#import "EOCEmployer.h"
复制代码
好处:
- 减少编译时间
- 降低彼此依赖程度
- 解决两个类互相引用的问题
必须要在头文件中引入其他头文件的情况
- 写的类继承自某个超类
- 写的类遵从某个协议(protocol)
第 3 条: 多用字面量语法,少用与之等价的方法
- 尽量使用字面量语法来声明NSNumber, NSArray, NSDictionary
NSNumber *someNumBer = @1;
NSArray *animals = @[@"cat", @"dog", @"mouse", @"badger"];
NSDictionary *personData = @{@"firstName": @"Matt",
@"lastName": @"Galloway",
@"age": @28};
复制代码
- 在NSArray, NSMutableArray, NSDictionary, NSMutableDictionary取值操作, 尽量使用字面量语法
NSString *animal = animals[1];
NSString *firstName = personData[@"firstName"];
复制代码
使用字面量语法的好处
- 使用字面量语法(literal syntax) 可以缩减源代码长度,使其更为易读
- 用字面量语法创建数组或字典时, 若值中为nil,则会立马抛出异常, 方便处理
第 4 条: 多用类型常量,少用#define预处理命令
类型常量和预处理指令都能达到定义常量的效果, 但二者有什么区别呢
- 预处理指令定义出来的常量没有类型信息,而且可被替换
- 类型常量包含类型信息, 不可别替换
类型常量
static const NSTimeInterval kAnimationDuration = 0.3;
//const 修饰,表示不可更改
//static 修饰,表示该变量仅在定义此变量的编译单元中可见
复制代码
对外公开某个常量
// in the header file
extern NSString *const EOCStringConstant;
//in the implementation file
NSString *const EOCStringConstant = @"VALUE";
复制代码
第 5 条: 用枚举表示状态, 选项, 状态码
在开发中,我们应该用枚举来管理一些我们定义的,能够组合在一起的状态,比如下面网络连接状态的枚举
typedef NS_ENUM(NSUInteger, EOCConnectionState) {
EOCConnectionStateDisconnected,
EOCConnectionStateConnecting,
EOCConnectionStateConected,
};
复制代码
在处理枚举类型的switch语句中不要实现default分支. 这样的话, 加入新枚举之后,编译器就会提示开发者:switch语句并未处理所有枚举
第 2 章 对象, 消息, 运行期
第 6 条: 理解"属性"这一概念
"属性"(property) 是Objective-C的一项特性,用于封装对象中的数据
存取方法
@interface EOCPerson : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end
//等同于
@interface EOCPerson : NSObject
- (NSString *)firstName;
- (void)setFirstName:(NSString *)firstName;
- (NSString *)lastName;
- (void)setLastName:(NSString *)lastName;
@end
复制代码
使用@dynamic关键字,将不会自动创建实现属性所用的实例变量,也不会为其创建存取方法
@interface EOCPerson : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end
@implementation EOCPerson
@dynamic firstName, lastName;
@end
复制代码
属性特质
原子性
- nonatomic: 非原子性的, 不使用同步锁
- atomic: 原子的, 加同步锁,确保原子性
atomic 与 nonatomic的区别
- 具备atomic特质的获取方法会通过所定机制来确保其操作的原子性.也就是说,如果两个线程读写同一属性,那么不论何时,总能看到有效的属性值
- 使用nonatomic, 那么当其中一个线程正在修改某个属性值时, 另外一个线程也许会突然闯入,把尚未修改好的属性值读取出来
在iOS开发中,所有的属性都声明为nonatomic, 这样做的原因是, 在iOS中使用同步锁的开销较大,这样会带来性能问题.但是在开发Mac OS X程序时, 使用atomic属性通常不会有性能瓶颈
读/写权限
- readwrite : 拥有 getter/setter方法
- readonly : 只拥有getter方法
内存管理语义
- assign 纯量类型(CGFloat, NSInteger)的简答赋值
- strong 拥有关系, 赋新值时, 先保留新值,并释放旧值,然后再将新值设置上去
- weak 未拥有关系, 赋新值,既不保留新值,也不释放旧值,在属性所指向的对象遭到摧毁时, 属性值会被清空
- unsafe_unretained 语义和assign相同, 适用于对象类型,非拥有关系,当目标对象遭到摧毁时, 属性值不会被清空
- copy 不保留新值,而是直接拷贝
第 7 条: 在对象内部尽量直接访问实例变量
在对象之外访问实例变量时,笔者建议在读取实例变量的时候采用直接访问的形式,而在设置实例变量的时候通过属性来做
- 直接访问实例变量的速度比较快,因为编译器所生成的代码会直接访问保存对象实例变量的那块内存
- 直接访问实例变量时, 会跳过set方法
- 直接访问实例变量,不会触发KVO
上述笔者说的方案, 要注意一下两个问题
- 在初始化方法及dealloc方法中, 总是应该直接通过实例变量来读写数据
- 有时会使用惰性初始化技术配置某份数据,这种情况下,需要通过属性来读取数据
第 8 条: 理解 "对象等同性" 这一概念
- 使用==操作符比较的是两个指针本身,而不是其所指的对象,应该使用NSObject协议中声明的"isEqual:"方法来判断两个对象的等同性
//如果已知两个对象是属于同一类,那么就可以使用==操作符
NSString *foo = @"Badger 123";
NSString *bar = [NSString stringWithFormat:@"Badger %i", 123];
BOOL equalA = (foo == bar) // NO
BOOL equalB = [foo isEqual: bar] //YES
复制代码
- 如果已知受测的两对象是NSSting类型, 请使用"isEqualToString: "方法比较,如果是NSArray,就使用"isEqualToArray: ",NSDictionary就使用"isEqualToDictionary: "
- 如果仅仅只是想比较其指针所指向的内容是否相同, 可以使用"isEqual:"方法
第 9 条: 以 "类族模式" 隐藏实现细节
在我们iOS开发中,其实也有用到"类族"这种模式,比如下面这个类方法
+ (UIButton *)buttonWithType:(UIButtonType)type;
复制代码
我们只需输入类型, 就能得到不同类型的button, 无需关心其内部是如何实现的
创建类族
以一个处理雇员的例子来说, 每个雇员都有自己的名字和工资, 而每个雇员的工作内容不同,然后这些东西我们都可以一个员工类来完成
typedef NS_ENUM(NSUInerger, EOCEmployeeType) {
EOCEmployeeTypeDeveloper,
EOCEmployeeTypeDesigner,
EOCEmployeeTypeFinance,
};
@interface EOCEmployee : NSObject
@property (copy) NSString *name;
@property NSUInerger salary;
+(EOCEmployee *)employeeWithType:(EOCEmployeeType)type;
-(void)doADaysWork;
@end;
@implementation EOCEmployee
+(EOCEmployee *)employeeWithType:(EOCEmployeeType)type
{
switch(type) {
case EOCEmployeeTypeDeveloper:
return [EOCEmployeeTypeDeveloper new];
break;
case EOCEmployeeTypeDesigner:
return [EOCEmployeeTypeDesigner new];
break;
case EOCEmployeeTypeFinance:
return [EOCEmployeeTypeFinance new];
break;
}
}
-(void)doADaysWork{
}
@end;
//实体子类
@interface EOCEmployeeTypeDeveloper : EOCEmployee
@end
@implementation EOCEmployeeTypeDeveloper
-(void)doADaysWork{
[self writeCode];
}
@end;
复制代码
第 10 条: 在既有类中使用关联对象存放自定义数据
通过关联对象的方式,我们可以给某个对象关联许多其他对象,这样的话, 我们就可以从某个对象中获取相应的关联对象的值
关联对象的相关语法
//为某个对象设置关联对象值
void objc_setAssociatedObject (id object, void *key, id value, objc_AssociationPolicy policy)
//从某个对象中获取相应的关联对象值
id objc_getAssociatedObject(id object, void *key)
// 移除指定对象的全部关联对象
void objc_removeAssociatedObjects(id object)
复制代码
关联对象用法举例
#import <objc/runtime.h>
static void *EOCMyAlertViewKey = "EOCMyAlertViewKey";
-(void)askUserAQuestion {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Question"
message:@"What do you want to do?"
delegate:self
cancelButtonTitle:@"Cancel"
otherButtonTitles:@"Continue", nil];
void (^block)(NSInteger) = ^(NSInteger buttonIndex) {
if (buttonIndex == 0) {
[self doCancel];
}else {
[self doContinue];
}
};
objc_setAssociatedObject(alert,EOCMyAlertViewKey,block, OBJC_ASSOCIATION_COPY);
[alert show];
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
void (^block)(NSInteger) = objc_getAssociatedObject(alertView, EOCMyAlertViewKey)
block(buttonIndex);
}
复制代码
第 11 条: 理解objc_msgSend的作用
在Objective-C中, 如果想某对象传递消息, 那就会使用动态绑定机制来决定需要调用的方法.在底层,所有方法都是普通的C语言函数,然后对象收到消息之后,究竟该调用哪个方法则完全于运行期决定, 甚至可以在程序运行是改变,这些特性使得Objective-C成为了一门真正的动态语言
给对象发消息可以这样写:
id returnValue = [someObject messageName:parameter];
复制代码
someObject 叫做"接收者"(receiver), messageName叫做"选择子"(selector). 选择子与参数合起来称为"消息"(message). 编译器看到此消息后, 将其转换为一条标准的C语言函数调用, 所调用的函数乃是消息传递机制中的核心函数,叫做objc_msgSend, 其原型如下:
void objc_msgSend(id self, SEL cmd, ...)
复制代码
第一个参数代表接收者, 第二个参数代表选择子,后续参数就是消息中的那些参数,其顺序不变
通过编译器转换得到以下函数
id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter);
复制代码
objc_msgSend函数会一聚接收者与选择子的类型来调用适当的方法. 为了完成此操作,该方法需要在接收者所属的类中搜寻其"方法列表",如果能找到与选择子名称相符的方法,就跳至其实现代码.若是找不到, 那就沿着继承体继续向上查找,等找到合适的方法之后再跳转.如果最终还是找不到相符的方法, 那就执行"消息转发"操作