数据的封装
对象除了收发消息外同样可以通过其成员变量来封装数据。本篇博客将讲述使用property声明变量时编译器所做的工作,以及一些需要注意的地方。
一个对象通常需要通过property来与其他对象保持联系。考虑两个对象之间的关系是非常重要的事情,因为这会涉及内存管理的问题。尽管当前有关内存管理方面大部分的工作都有ARC代替我们处理了,但是我们还是很有必要去学习一下这方面的知识去避免一些问题,例如强引用循环,如果处理不好这将导致内存泄露。本篇博客将讲述对象的声明周期以及如何合理的管理他们。
声明公共属性
[code lang=”objc”]
@interface XYZPerson : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end
[/code]
使用访问方法来设置或获取属性值
[code lang=”objc”]
NSString *firstName = [somePerson firstName];
[somePerson setFirstName:@"Johnny"];
[/code]
通常这些访问方法都是由编译器根据synthesized自动生成的,因此只需要声明@property就可以生成set和get方法。
synthesized方法将遵循下列方法
getter方法的名字与属性名字相同如@property NSString *name;则getter方法是name。
setter方法的名字则在属性名前加set,如setName。
如果不想通过set方法修改属性值则添加readonly如:@property (readonly) NSString *name;
注意如果不写readonly那么默认的就是readwrite。
如果想改变getter或setter方法的名字可以做下列操作:
@property (getter=isFinished) BOOL finished;
self.isFinished or self.finished都可以访问finished变量。
但多数属性背后都有一个实体变量
@property NSString *name;
我们可以使用self.name访问属性name也可以通过_name来访问name属性。
@property (readonly)NSString *name;
可以通过self.name和_name的来访问,如果通过self.name=@”something”编译器会报错。但是_name=@”something”则正常编译。
在上述的例子中name成为属性,_name成为实体变量(默认方式)。该实体变量通常是在对象创建时被创建,对象销毁时被销毁。
你可以通过如下方式来改变属性所对应的变量名
[code lang=”objc”]
@implementation YourClass
@synthesize propertyName = instanceVariableName;
…
@end
[/code]
如果直接@synthesize firstName;那么属性firstName对应的变量名字也叫firstName。
你也可以不使用属性来定义变量:
[code lang=”objc”]
@interface SomeClass : NSObject {
NSString *_myNonPropertyInstanceVariable;
}
…
@end
@implementation SomeClass {
NSString *_anotherCustomInstanceVariable;
}
…
@end
[/code]
用initializer方法来初始化变量
setter方法会有一些额外的副作用,他会触发KVC通知,或者执行一些你给他设定好的代码。在初始化方法中我们应该直接地去访问一个变量,因为在这个时候property已被设置但是对象的其他部分可能没有完全的初始化。
一个典型的初始化方法应该如下所示:
[code lang=”objc”]
– (id)init {
self = [super init];
if (self) {
// initialize instance variables here
}
return self;
}
[/code]
init方法能够调用它父类的init方法并将结果返回给self,下图展示了该过程:
初始化方法也可以赋值:
[code lang=”objc”]
– (id)initWithFirstName:(NSString *)aFirstName lastName:(NSString *)aLastName {
self = [super init];
if (self) {
_firstName = aFirstName;
_lastName = aLastName;
}
return self;
}
[/code]
实现自定义的访问方法
property的背后不总是有实体变量的支持,如XYZPerson定义一个只读的属性fullname:
@property (readonly) NSString *fullName;
当firstName和lastName发生变化后,每一次都要去更新fullName属性,这总让人感觉麻烦,这里有种简便的方法那就是自定义属性的访问方法,如下例所示:
[code lang=”objc”]
– (NSString *)fullName {
return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];
}
[/code]
如果你需要自定义一个属性的访问方法并在其中用到了该属性的实例变量,那么在该方法中你用该直接去访问该变量。下面给出的例子的用法叫做懒惰初始化,即当你去访问一个变量是,该变量才被初始化。
[code lang=”objc”]
– (XYZObject *)someImportantObject{
if(!_someImportantObject){
_someImportantObject = [[XYZObject alloc] init];
}
return _someImportantObject;
}
[/code]
属性默认是具有原子性的
原子性是主要考虑多线程问题,如果没有原子性那么当多个线程同时访问一个变量时会出现一系列的问题。但原子性增加了时间开销,因此在IOS我们为了获得更快的访问速度通常在属性名前添加nonatomic,@property (nonatomic) NSObject *name;
通过所属关系和职责来管理对象图
自前面的文章中我们已经了解了OC是如何动态开辟内存的,那么接下来我们需要了解一下对象的生命周期。
现在先不考虑如何手动的去管理内存,我们先来考虑一下对象之间的关系。
如下图所示的强引用关系:
当XYZPerson对象从内存中释放掉,那么这两个string对象也将从内存释放掉。
现在我们将上述情况变得更复杂一些,如下图:
当用户点击update按钮时将会根据相关的名字来更新预览界面。其对象图如下:
此时是用户第一次键入用户名并点击预览按钮
当用户重新填写firstname但没有点击更新时的对象图如下所示:
当用户点击更新按钮后,视图将重新显示新的信息,此时的对象图如下:
此时@”John”对象不在被强引用指针指向那么它就会从内存中被删除。
要避免强引用循环
当两个对象相互引用时你就需要引起注意了,如果是两个对象相互引用且是强引用关系那么就会发生内存泄露,此时的对象图如下所示:
当其它对象分别放弃他们指向的table view和delegate后对象图如下所示:
这时候两个对象已经没有在内存中存在的必要性了,因为已经没有外界的强引用指针指向他们了,但是这两个对象之间是强类型互相引用的关系所以他们还存在于内存中,这就是所谓的强引用循环。