1.属性封装一个对象的值
大多数对象需要跟踪信息来执行他们的任务,一些对象被设计为封装一个或多个值,例如一个NSNumber类用来保存一个数值,或者一个自定义类型的对象的一些数值。
1.1为可以公开的数据声明公有属性
Objective-C属性提供了一种定义类信息的方法,目的是数据的封装。例如:
@interface XYZPerson : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end
在这个例子中,
XYZPerson
类定义了字符串类型的属性用来存储人的名字。
鉴于面向对象编程中的一个重要原则是:一个对象应该在一个公共接口背后隐藏其内部运作,通过公开的对象访问属性而不是直接试图通过内部的值去访问,这是很重要的。
1.2 使用Getter和Setter方法来获取和设置属性值
NSString *firstName = [somePerson firstName];
[somePerson setFirstName:@"Johnny"];
默认情况下,这些访问器方法是由编译器自动合成(synthesize)的,所以
除了
在类的接口中
使用
@property
声明属性外,
你不需要做任何事情
。
1.2.1 命名规则
1)一个属性的getter方法,与属性的名字相同。例如:
属性firstname的getter方法,其名字就叫做
firstname。
2)一个属性的setter方法以“set”开头,后面跟上属性的名称,例如:
属性firstname的setter方法,其名称为setFirstName。
如果你不希望通过setter方法改变一个属性,可以将属性设为可读的。
@property (readonly) NSString *fullName;
和只读readonly相对应的是readwrite属性,是可读写的。
1.2.2 给访问器方法设置不同的名字
可以在声明属性的时候,为属性的访问器方法制定名字。
例如:
@property (getter == isFinished) BOOL finished;
这行代码将BOOL类型的finished属性制定它的getter方法名称为isFinished。
也可以这样指定属性是可读的:
@property (readonly,getter == isFinished) BOOL finished;
1.3点表达式
Objective-C 2.0 提供了新的语法特性:点语法,来访问对象的属性。
点表达式出现在等号的左边,该属性的setter方法将被调用;
点表达式出现在等号的右边,该属性的getter方法将被调用。
1)访问属性值:
somePerson.firstName 等同于 [somePerson firstName];
2)设置属性值:
somePerson.firstName = @“qqq”; 等同于[somePerson setFirstName:@“qqq”];
注意:如果一个对象是只读的,那么当你使用.语法的时候,会得到一个编译器错误。也就是说,只读属性不能使用点语法。
1.4实例变量
默认情况下,可读写的属性是有辅助实例变量的,由编译器自动合成.
除非特别制定实例变量名称,否则,实例变量的名称与属性的名称相同,只是在名称前面加了一个短下划线。短下划线前缀清楚地表明了,你访问的是一个实例变量,而不是一个局部变量或其他什么变量。
代码:
-(void)someMethod
{
NSString *myString = @“A string”;
_someString = myString;
}
在这段代码中,myString是局部变量,_someString是实例变量。
1.4.1自定义合成实例变量(synthesized instance)的名字
默认的实例变量名称是属性名称前面加上一个短下划线前缀,_propertyName;
也可以用下面的语法自定义实例变量的名称:
@implementation YourClass
@synthesized propertyName = instanceVariableName;
……
@end
例如:
@synthesized firstName = ivar_firstName;
这种情况下,属性名称仍然是firstName,可以通过ivar_firstName访问getter和setter方法。
重点:
如果使用@synthesize,但是不指定实例变量的名字,例如:
@synthesize firstName;
那么实例变量的名称与属性的名称相同。使用的时候不需要加下划线前缀。
1.4.2 定义没有属性的实例变量
在一个对象中使用属性,在任何你想追踪一个值或者一个其他的对象的时候。
如果你需要定义一个自己的实例变量,而不想声明它的属性,你可以将他们添加在类接口或者类实现的顶部的大括号内。如下:
@interface SomeClass:NSObject{
NSString *_myNonePropertyInstanceVariable;
}
...
@end
或者
@implementation SomeClass{
NSString *_myNonePropertyInstanceVariable;
}
...
@end
注意:也可以在类的扩展的文件的头部添加没有属性的实例变量。
1.5直接从初始化方法中访问实例变量
Setter方法可能有额外的副作用,如果你编写了自定义的方法,它们可能会引发新的KVC通知,或者执行进一步的任务。
应该通过初始化方法直接访问实例变量,因为当一个属性被设置的时候,其余的对象可能尚未被完全初始化。即使你没有提供自定义的访问器方法或知道任何副作用,在未来也可能被其他子类覆盖。
一个典型的init方法:
-(id)init
{
self = [super init];
if(self)
{
//在这里初始化实例变量
}
return self;
}
在调用父类的初始化方法的时候,会层层调用父类的父类的初始化方法,当所有的初始化都完毕的时候,再初始化自己。
1.5.1 指定的初始化方法是主要的初始化方式
如果一个对象声明一个或多个初始化方法,你应该决定哪些方法是指定的初始化方法。这可以提供很多可选的初始化(例如可以有很多的参数),并且可以为其他的方法调用提供便利,你通常应该通过使用合适的默认值调用指定的初始化方法来覆盖init方法。
如果一个XYZPerson类也有一个出生日期属性,那么指定的初始化方法可能是:
如果仍然希望提供只有firstName和lastName两个参数的初始化方法,可以通过调用上面的方法实现,如下:
-(id)initWithFirstName:(NSString *)aFirstName lastName:(NSString *)aLastName
{
return [self initWithFirstName:aFirstName lastName:aLastName dateOfBirth:nil];
}
也可以实现一个标准化的初始化方法来提供合适的默认值:
-(id)init
{
return [self initWithFirstName:@“John" lastName:@“Doe" dateOfBirth:nil];
}
当
你子类化一个拥有多个初始化方法的类的时候,如果需要编写一个初始化方法,你应该覆盖超类的指定的初始化方法来执行自己的初始化,或者添加自己的额外的初始化方法。不管怎样,在做你自己的初始化定义之前,你应该调用超类的指定的初始化方法。
1.6实现自定义的访问器方法
属性并不总是有自己的实例变量。
例如,XYZPerson类可能会定义一个只读属性:一个人的全名:
@property (readonly) NSString *fullName;
相比较每次姓或者名发生改变的时候更新全名,只要编写一个自定义的访问器方法来构建完整的名称显得非常简单,如下:
-(NSString *)fullName
{
return [NSString stringWithFormat:@“%@ %@“,self.firstName,self.lastName];
}
如果你需要使用一个实例变量为一个属性编写一个自定义的访问器方法,你必须在在内部方法中直接访问实例变量。例如,常见的延迟属性的初始化的作法,如下:
-(XYZObject *)someImportantObject
{
if(!_someImportantObject)
{
_someImportantObject = [[XYZObject alloc] init];
}
return _someImportantObject;
}
在返回值之前,这种方法先检查实例变量_someImportantObject 是否为nil,如果为nil,则为它分配一个对象。
注意:在任何情况下,编译器都会自动合成一个实例变量,同时也会自动合成至少一个访问器方法。如果你实现了一个可读写属性的getter和setter方法,或者一个只读属性的getter方法,那么编译器会认为你正在控制着属性的实现,并且不会再自动合成实例变量。
如果你仍然需要一个实例变量,你需要申请一个合成:
@synthesize property = _property;
1.7 属性默认是原子的(atomic)
@interface XYZObject:NSObject
@property NSObject *implicitAtomicObject; //atomic by default
@property (atomic) NSObject *explicitAtomicObject; //explicitly marked atomic
@end
原子的特性,意味着合成访问器确保一个值总是完全通过getter方法检索或者完全通过setter方法设置,即使从不同的线程同时访问访问器。
因为一个原子访问器方法的内部实现和同步合成是私有的,用你自己实现的访问器方法来把一个访问器合成是不可能的。如果你尝试为一个原子的可读写的属性提供一个自定义的setter方法,但是却交由编译器合成getter方法,那么
你会得到一个编译器警告。
你可以使用nonatomic属性指定合成访问器,来直接简单地设置或返回一个值。但能保证从不同的线程同时访问相同的值。由于这个原因,访问nonatomic属性比访问atomic属性更快,并且很好地将你自己实现的getter方法和合成的setter方法结合。:
@interface XYZObject : NSObject
@property (nonatomic) NSObject *nonatomicObject;
@end
@implementation XYZObject
-(NSObject *)nonatomicObject
{
return _nonatomicObject;
}
//setter will be synthesized automatically
@end
注意:属性的原子性并不等同于对象的线程安全。
比如,从一个线程中使用原子特性的访问器修改一个XYZPerson对象的firstName 和 lastName,如果另一个线程在同一时间访问了这两个属性,原子特性的getter方法将返回一个完整的字符串(没有崩溃),但不能保证这些值对于彼此来说是相对正确的名称,如果在改变之前访问了firstName,但是在改变之后访问了lastName,那么你将会得到不一致不匹配的名称。
2.通过所有权和责任管理对象图
你已经看到了,Objective-C中对象是在堆中被动态分配内存的,这就意味着你需要使用指针来跟踪对象的地址。与标量值不同,它并不总是能够通过一个指针变量的范围确定对象的生命周期。相反,只要一个对象被其他对象需要,这个对象就必须在内存中保持活跃。
相比去担心手动管理每一个对象的生命周期,不如去考虑对象之间的关系。
例如,XYZPerson对象的两个字符串类型的属性firstName和lastName,实际上是被XYZPerson实例所“拥有”的。这意味着,只要XYZPerson实例保留在内存中,这两个属性也应该保留在内存中。
当一个对象以这种方式依赖于其他对象,并有效地拥有其他对象的使用权,就说第一个对象 强引用 另一个对象。在Objective-C中,一个对象只要有至少一个对象强引用它,它就在内存中保留。
强引用的声明:_strong。
默认情况下,Objective-C属性和变量对他们的对象保持强引用关系,很多情况下这是没有问题的,但是这会造成一个潜在的问题:强引用循环。
2.1 使用强或弱的声明来管理所有权
默认情况下,对象的属性声明如下:
@property id delegate;
不需要显示致命strong属性,因为默认属性是strong的。
声明一个弱引用,如下:
@property (weak) id delegate;
2.2 避免强引用循环
在强引用中,有时候会出现循环引用的情况,这时就需要弱引用来帮忙(_weak)。
2.2.1强引用和弱引用的区别:
- 强引用持有对象,弱引用不持有对象。
- 强引用可以释放对象,但弱引用不可以,因为弱引用不持有对象,当弱引用指向一个强引用所持有的对象时,当强引用将对象释放掉后,弱引用会自动的被对象赋值为nil,即弱引用会自动的指向nil。
2.3 Copy属性
某些情况下,一个对象可能希望保留任何被设置为其属性的对象。
任何你想设为copy属性的对象都必须支持NSCopying协议。
协议的描述在
Protocols Define Messaging Contracts
. 如果想了解更多关于NSCopying的内容,去看
Advanced Memory Management Programming Guide
2.3.1 copy属性和retain属性、assign属性的区别
- 当使用copy属性时,setter方法会先release掉旧值,然后开辟一块新的内存,复制对象的内容,引用计数为1(减少了对上下文的依赖)
- 当使用retain属性时,setter方法也是先release掉旧值,同时会开辟一块新内存存放对象,引用计数加1来管理内存。
- 当使用assign属性时,地址和内容都是一样的,引用计数不变,对旧对象来说只是设置了一个别名而已。