编写高质量iOS的52个有效方法学习笔记(6-12)
C语言是面向过程的语言,Objective-C语言是面向对象的语言,“对象”是基本构造单元,开发者可以通过对象来存储并传递数据。对象之间传递数据并执行任务的过程称为“消息传递”。
当应用程序运行起来以后,为其提供相关技术支持的代码叫做“Objective-C 运行期环境”(Objective-C runtime),它提供了一些使得对象之间传递消息的重要函数,并且包含创建类实例的全部逻辑。
6.理解“属性”这一概念
“属性”(property)是Objective-C的一项特性,用于封装对象中的数据。
@property (nonatomic,copy,readonly) NSString *sex;
这种写法包含了getter和setter方法,使用点语法可以来调用getter和setter方法
self.sex = @"男"; //same as:
[self setSex:@"男"];
NSString *sexStr = self.sex; //same as:
NSString *sexStr = [self sex];
属性的几个特性:原子性、读写性、语义性
对于atomic的属性,系统生成的 getter/setter 会保证 get、set 操作的完整性,不受其他线程影响。比如,线程 A 的 getter 方法运行到一半,线程 B 调用了 setter:那么线程 A 的 getter 还是能得到一个完好无损的对象。
而nonatomic就没有这个保证了。所以,nonatomic的速度要比atomic快。
-
Atomic
是默认的
会保证 CPU 能在别的线程来访问这个属性之前,先执 行完当前流程
速度不快,因为要保证操作整体完成 -
Non-Atomic
不是默认的
更快
线程不安全
如有两个线程访问同一个属性,会出现无法预料的结果
读写特性:readwrite拥有getter(获取方法)与setter(设置方法),若该属性由@synthesize实现,则编译器会自动生成这两种方法,@synthesize在.m中声明,系统默认是有的,可以不写。
readonly(只读)仅有获取方法
语义特性
1,当把语义特性声明为assign时,setter和getter时方法内部实现
- (void)setName:(NSString *)name{
_name = name;
}
- (NSString *)name{
return _name;
}
2,当把语义特性声明为retain时,setter和getter方法内部实现
- (void)setName:(NSString *)name{
if (_name != name) {
[ _name release];
_name = [name retain];
}
}
- (NSString *)name{
return [[ _name retain] autorelease];
}
3,当把语义特性声明为copy时,setter和getter方法内部实现
- (void)setName:(NSString *)name{
if (_name != name) {
[ _name release];
_name = [name copy];
}
}
- (NSString *)name{
return [[ _name retain] autorelease];
}
- 可以用@property语法来定义对象中所封装的数据
- 通过“特性”来指定存储数据所需的正确语义
- 在设置属性所对应的实例变量时,一定要遵循属性所声明的语义
- 开发iOS程序时应该使用nonatomic属性,因为atomic属性会严重影响性能
7、在对象内部尽量直接访问实例变量
在对象之外访问实例变量时,总是应该通过属性来做,然而在对象内部访问实例变量时的建议是:在写入实例变量时通过其“设置方法”来做,而在读取实例变量时,则直接访问之。之所以要通过“设置方法”来写入实例变量,其首要原因在于,这样做能够确保相关属性的“语义特性”得以贯彻。
需要注意的两种情况是:1、在初始化方法中应该直接访问实例变量,因为子类可能会“覆写”(overrede)设置方法。2、懒加载。必须通过“获取方法”来访问属性,否则,实例变量就永远不会初始化。
以上获取方法指的就是点语法。
- 在对象内部读取数据时,应该直接通过实例变量来读,而写入数据时,则应通过属性来写。
- 在初始化方法以及dealloc方法中,总是应该直接通过实例变量来读写数据
- 使用懒加载的话必须用点语法操作。
8、理解“对象等同性”这一概念
- (BOOL)isEqual:(id)object {
if ([self class] == [object class]) {
return [self isEqualToPerson:(Person *)object];
}else{
return [super isEqual:object];
}
}
- (BOOL)isEqualToPerson:(Person *)person {
if (self == object) {
return YES;
}
if (![_firstName isEqualToString:person.firstName]) {
return NO;
}
if (![_lastName isEqualToString:person.lastName]) {
return NO;
}
if (_age != person.age ) {
return NO;
}
return YES;
}
- 若想检测对象的等同性,请提供“isEqual:”与hash 方法。
- 相同的对象必须具有相同的哈希码,但是两个哈希码相同的对象却未必相同。
- 不要盲目的逐个检测每条属性,而是应该依照具体需求来指定检测方案。
- 编写hash方法时,应该使用计算速度快而且哈希码碰撞几率低的算法。
9、以“类族模式”隐藏实现细节
“类族”是一种很有用的模式,可以隐藏“抽象基类”背后的实现细节。类方法,(工厂模式)
- 类族模式可以把实现细节隐藏在一套简单的公共接口后面。
- 系统框架中经常使用类族。
- 从类族的公共抽象基类中继承子类时要当心,若有开发文档,则应首先阅读。
10、在既有类中使用关联对象存放自定义数据
有时需要在对象中存放相关信息、这时我们通常会从对象所属的类中继承一个子类,然后改用这个子类对象。然而并非所有情况下都能这么做,有时候类的实例可能是由某种机制创建的,而开发者无法令这种机制创建出自己的子类实例。Object-C中有一项强大的特性可以解决这个问题,这就是“关联对象”(Associated Object)
关联类型 | 等效的@property |
---|---|
OBJC_ASSOCIATION_ASSIGN | assign |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | nonatomic,retain |
OBJC_ASSOCIATION_COPY_NONATOMIC | nonatomic,copy |
OBJC_ASSOCIATION_RETAIN | retain |
OBJC_ASSOCIATION_COPY | copy |
下列方法可以管理关联对象:
void objc_setAssociatedObject (id object,void *key,id value,objc_AssociationPolicy policy)
此方法以给定的键和策略为某对象设置关联对象值。
id objc_getAssociatedObjects(id object,void *key)
此方法根据给定的键从某对象中获取相应的关联对象值。
void objc_removeAssociatedObjects(id object)
此方法移除指定对象的全部关联对象。
关联对象用法举例:
ios 开发时经常用到UIAlertView类,该类提供了一种标准试图,用来提示信息。但是创建的代码和处理按钮动作的代码是分开的,如:
- (void)viewDidLoad {
UIAlertView *view = [[UIAlertView alloc] initWithTitle:@"标题" message:@"消息" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"1", nil];
[view show];
}
#pragma mark --UIAlertViewDelegate
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == 0) {
NSLog(@"0");
}else{
NSLog(@"1");
}
}
如果想在同一个类里处理多个警告信息视图,那么代码就会变得更加复杂,我们必须在delegate方法中检查传入的alertview参数,并据此选用相应的逻辑。要是能在创建视图的时候就直接把处理每个按钮的逻辑都写好,就简单多了。这可以通过关联对象来做。创建完视图后,设定一个与之关联的“块(block)”等到执行delegate方法时再将其读出来,如下:
#import <objc/runtime.h>
static void *key = @"key";
- (void)viewDidLoad {
UIAlertView *view = [[UIAlertView alloc] initWithTitle:@"标题" message:@"消息" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"1",@"2", nil];
void (^block)(NSInteger) = ^(NSInteger buttonIndex){
if (buttonIndex == 0) {
NSLog(@"0");
}else{
NSLog(@"1");
}
};
objc_setAssociatedObject(view, key, block, OBJC_ASSOCIATION_COPY);
[view show];
}
#pragma mark --UIAlertViewDelegate
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
void (^block)(NSInteger) = objc_getAssociatedObject(alertView, key);
block(buttonIndex);
}
以这种写法,创建和处理的代码放在一起,比较方便查阅。但是,采用该方法时需要注意:块可能要捕获某些变量,也许会造成“循环引用”。
- 可以通过“关联对象”机制来把两个对象连起来。
- 定义关联对象时可指定内存管理语义,用以模仿定义属性时所采用的“拥有关系”与“非用有关系”。
- 只有在其他方法不可行时才应选用关联对象,因为这种做法通常会引入难于查找的bug。
11、理解objc_msgSend的作用
id returmValue = [someObject messageName:parametter];
someObject叫做“接收者”,messageName叫做“选择子(selector)”。选择子和参数合起来称为“消息(message)”。编译器看到后将其转换成标准的C语言函数调用,所调的函数乃是消息传递机制中的核心函数,叫做objc_msgSend,其“原型”如下:
void objc_msgSend(id self,SEL cmd,……)
第一个参数是接收者,第二个参数是选择子,后续参数就是消息中那些参数,其顺序不变。
在实际开发中,大家无须担心这一问题,不过应该了解其底层工作原理。
12、理解消息转发机制
- 若对象无法响应某个选择子,则进入消息转发流程
- 通过运行期的动态方法解析功能,我们可以在需要用到某个方法时再将其加入类中。
- 对象可以把其无法解读的某些选择子转交给其他对象来处理
- 经过上述两步之后,如果还是没办法处理选择子,那就启动完整的消息转发机制。