版权声明:原创做品,谢绝转载!不然将追究法律责任。html
不少对象须要跟踪信息为了执行他们的任务。一些对象设计模型一个或者多个值。例如NSNumber 类用来保存一个值或者自定义的类有一些属性。有一些对象不在通常的范围内。也许处理界面交互和一些信息展现这些对象用来跟踪界面元素或者相关的模型对象。ios
声明公有的属性公开数据:web
Objective-c属性提供了一个定义类信息的方法目的是数据的封装设计模式
@interface XYZPerson : NSObject安全
@property NSString *firstName;app
@property NSString *lastName;ui
@endatom
在这个例子中这个XYZPerson 类定义了String类型的属性用来存储人的名字spa
在面向对象思想中一个只要的原则是对象应该隐藏内部实如今公有接口的后面。咱们访问对象的属性而不是直接访问内部的值。线程
咱们用设置器和访问器方法来获取和设置属性的值
NSString *firstName = [somePerson firstName];
[somePerson setFirstName:@"Johnny"];
默认状况下这些设置器和访问器方法是自动合成的由编译器,所以你不须要作任何事情除了声明属性。
合成的方法遵循特定的命名规范:
这个方法用来访问值(设置器方法)和属性有相同的名字。
例如:firstName 的访问器方法名字就叫作firstName
这个方法用来设置值(设置器方法)开始用set而后用上属性的名字第一个字母大写:
例如:setFirstName;
若是你不但愿属性改变那么就用readonly只读的:
@property (readonly)NSString *fullName;
关键字显示对象怎么和属性交互,以及告诉编译器怎么合成相关的设置器和访问器方法。
例如这个编译器将要合成fullName访问器方法,可是没有setFullName方法。
和只读readOnly相反的是readwrite属性默认是可读写的。
若是你想给访问器方法设置不一样的名字,你能够再给属性添加他的属性的时候指定名字。例如Boolean属性(这个属性有YES 或者NO值)给他自定义访问器方法以“IS“开头
@property (getter = isFinished)BOOL finished;
若是你有多个属性这样设置:
@property (readonly,getter = isFinished)BOOL finished;
在这个例子中编译器合成的是isFinished方法而不是setFinished方法。
点语法是调用的访问器方法
除了明确的访问器方法调用Objective-c还提供了点语法访问对象的属性。
点语法让你像这样访问属性:
NSString *firstName = somePerson.firstName;
点语法纯粹是一个包装器方法用来调用访问器方法的。当用逗号时候,属性仍然能够被访问和修改用getter和setter方法例如:
访问值用somePerson.firstName 和 [somePerson firstName]:是同样的。
设置值用somePerson.firstName = @"qiqi";和
[somePerson setFirstName:@“qiqi”];
这就意味着属性的访问用点语法也能够控制属性。若是属性是readonly编译器将要获得一个警告当你用逗号的时候。
大多数属性都有实例变量。
默认状况下,可读写的属性是有辅助实例变量的,再次由编译器自动合成。
实例变量是一个变量,在整个对象生命周期内都存在。当对象第一次被建立的时候,用于实例变量的内存分配,当对象销毁的时候就被释放了。除非有特别的指示不然合成的实例变量和属性有相同的名字,可是有一个下划线例如属性叫作firstName。合成的实例变量的名字叫作_firstName.
虽然他的最佳实践是一个对象访问本身的属性用访问器或者点语法,他也有可能在实现文件里直接访问实例变量用实例方法。下划线加上实例变量代表你访问的是实例变量而不是局部变量例如:
- (void)someMethod {
NSString *myString = @"An interesting string";
_someString = myString;
}
这个例子很明显一个是局部变量和一个实例变量。
一般咱们应该用访问器方法和点语法来进行属性的访问即你访问对象的属性用他们本身的实现,在这个例子中咱们能够用self:
-(void)someMethod
{
NSString *myString = @"qiqi";
self.someString = myString;
//or
[self setSomeString:myString];
}
这个特殊的例外是当写初始化方法或者销毁方法或者自定义访问方法咱们后来描述的。
你能够自定义合成实例变量的名字
@implementation YourClass
@synthesize propertyName = _instanceVariableName;
@end
例如:
@sythesize firstName = _my_firstName;
在这个例子中,属性将要被叫作firstName,而且能够访问经过firstName而且setFirstName访问器方法或者点语法,可是有辅助实例变量_my_firstName
重要注意:若是你用@synthesize没有指定实例变量的名字,就像:
@sytheSize firstName;
这个实例变量将要和你的属性的名字同样。
在这个例子中实例变量叫作firstName没有下划线。
你能够定义实例变量而不用属性
属性的最佳实践在对象上是任什么时候候你须要跟踪对象的值或者其余的对象。
若是你须要定义你本身的实例变量没有定义属性,你能够在头文件或者实现文件里用以下:
@interface SomeClass:NSObject
{
NSString *_myInstanceVariable;
}
@implementation SomeClass
{
NSString *_anotherCustomInstanceVarible;
}
@end
你能够添加实例变量在延展里
访问实例变量直接从初始化方法里:
setter方法能够有额外的反作用。他们能够出发KVC通知,或者执行进一步的任务若是是你的自定义方法。
你应该直接访问实例变量从你的初始化方法里应为属性在这时候已经被设置了。其他的对象可能还没有初始化完成。即便你不提供自定义的访问器方法在将来的时候可能被其余的子类给覆盖。
一个典型的init方法。
- (id)init {
self = [super init];
if (self) {
// initialize instance variables here
}
return self;
}
一个init方法应该分派self和父类初始化方法在作本身的初始化以前。因为父类可能初始化失败或者返回nil所以咱们应该检查来确认self不是nil在咱们本类初始化时候。
在调用父类初始化方法的时候会层层调用父类的初始化方法,当都初始化完毕的时候那么再初始化本身。
前面咱们看的对象初始化除了调用init方法外,或者调用方法初始化特殊值。
- (id)initWithFirstName:(NSString *)aFirstName lastName:(NSString *)aLastName;
你能够实现这个方法像这样:
- (id)initWithFirstName:(NSString *)aFirstName lastName:(NSString *)aLastName {
self = [super init];
if (self) {
_firstName = aFirstName;
_lastName = aLastName;
}
return self;
}
指定初始化是主要的初始化方法:
若是一个对象一个或者多个初始化方法,你应该定义哪一个方法是设计的初始化者,这个提供了不少可选的初始化(例如能够有不少的参数),而且能够为其余的方法调用提供便利,你应该也典型重载初始化方法来给你的初始化方法设定默认的值。
若是下面这个方法的类有生日的属性,咱们设计初始化方法可能这样:
- (id)initWithFirstName:(NSString *)aFirstName lastName:(NSString *)aLastName dateOfBirth:(NSDate *)aDOB;
这个方法将要设置咱们的实例变量,若是你将提供便利初始化第一个和第二个名字,你将要这样实现你的初始化方法:
- (id)initWithFirstName:(NSString *) aFrameworkName lastName:(NSString *)aLastName
{
return [self initWithFirstName:aFirstName lastName:aLastName dateOfBirth:nil];
}
你能够实现标准的init方法来提供适当的默认值
- (id)init
{
return [self initWithFirstName:@"qiqi" lastName@"deo" dateOfBirth:nil];
}
你须要写初始化方法当子类用多个初始化方法,你应该重写父类的初始化方法,来实现本身的初始化,或者添加本身的初始化方法,可是都得初始化父类在初始化本身以前。
你能够实现本身的访问器方法:
属性不能一直有本身的辅助实例变量。
举一个例子咱们为了跟踪属性的值能够自定义打印出他的值例如:
- (NSString *)fullName {
return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];
}
若是你须要写自定义的访问器方法为属性而且有一个实例变量。你必须直接定义实例变量。例如:咱们常常在属性里面延迟初始化实例变量懒加载设计模式:
- (XYZObject *)someImportantObject {
if (!_someImportantObject) {
_someImportantObject = [[XYZObject alloc] init];
}
return _someImportantObject;
}
再返回值以前须要检测返回值是否是nil,若是为空那么就初始化对象。
编译器会自动合成一个实例变量在全部的状况下,它也是合成只要一个访问器方法。若是你本身实现访问器和设置器方法,那么编译器会认为你本身控制属性的实现,而不会自动的生成实例变量。
若是你仍然须要实例变量的话,你将要须要请求一个合成。
@synthesize property = _property;
属性默认的是原子性的:
默认的Objective-c属性是原子性的:
@interface XYZObject : NSObject
@property NSObject *implicitAtomicObject; // atomic by default
@property (atomic) NSObject *explicitAtomicObject; // explicitly marked atomic
@end
注意:属性的原子性的特性并非意味着这个对象的线程是安全的。咱们想象一下,一个对象有名字和姓在原子性的访问器被访问。若是一个线程同时访问他们的名和姓,这个原子性的访问方法不必定正确返回值。若是名字在访问以前改变,姓在访问以后改变,你会有一个不一致,不对应的结果,那么这不是你想要的结果。
经过对象的全部权和责任管理对象图
就像你已经看到的,Objective-c的对象是动态建立的在堆上,这意味着你须要用指针来跟踪对象地址,不像常量,不是一直能肯定对象的生命周期的范围经过指针变量,相反的是一个对象必须长时间保存在内存中以便于其余的对象引用。
不要试图担忧手动管理每一个对象的生命周期,你应该试图考虑对象之间的关系。
例如这个XYZPerson对象,例如,有两个名字的属性有效的被XYZPerson实例拥有,这意味着他们应该呆在内存足够长只要XYZPerson在内存中。
当一个对象依赖其余的对象用这种方式,有效的采起其余对象的全部权,这个第一个对象强引用其余的对象。在Objective-c一个对象保持存活状态只要他最少有一个强引用指向他从别的对象:
当XYZPerson对象被销毁从内存,这两个对象也将要被销毁,假设没有其余的强引用指向他们。
下面有一个更复杂的例子:
当我点击更新的按钮,这个 badge preview更新相关名字信息。
第一次人的详细信息被输入和更新以下图:
当用户修改这我的的名字变成以下:
这个View管理一个强引用关系到字符串对象尽管XYZPerson有不一样的名字,这意味这这个字符串对象将在内存中被View来打印和使用。
当第二次点击的时候这个View被告诉来更新内部的属性匹配人的对象。所以变成以下图:
到如今,这个字符串对象没有强引用指向他,他就会被从内存中移除掉。
默认的Objective-c的属性和实例变量维持着强引用指向其余对象, 这只是在合适的状况下,可是会引发潜在的问题强引用循环。
怎么避免强引用
尽管强应用适合单向对象之间的关系,你须要当心处理组相互关联的对象,若是一个组的对象组有一圈强引用,他们相互存活即便他们没有强引用从组的外部。
一个明显的例子在tableView和他的delegate以前有潜在的强引用存在。为了使通用的tableView这个类在不少环境能用到。他的delegate作出一些决定给外部对象。这意味着他表现什么依赖与另外一个对象,或者作什么若是用户和表示图交互。下图:
这样将会出现一个问题若是其余的对象放弃对tableview和delegate以下图:
即便不须要对象们存活在内存中,没有强引用指向tableView或者delegate不一样于两个对象之间的关系,剩下的两个强引用保持着他们之间的关系。可是这是个强引用的循环。
有一个方法来解决这个问题就是替代其中一个强引用为弱引用。弱引用并不意味着两个对象以前的全部权,而且不能保持一个对象的存活。
如今变成这样下图:
当没有其余对象给tableView或者tableView的delegate,就没有强引用指向delegate对象以下图:
这意味着delegate对象将要被销毁,从而释放了tableView的强引用。
一旦delegate被销毁,就没有其余的强引用指向tableView,所以他也被销毁。
使用强和弱来管理全部权
默认状况下,对象属性这样声明
@property id delegate;
用强引用来实现实例变量,声明一个弱引用,给他添加属性想这样:
@property (weak)id delegate;
注意:weak的相反是strong。因此没有必要声明,默认是strong
局部变量(不是属性的实例变量)是默认保持强引用的,这意味着下面代码将要像你预想的这样工做:
NSDate *originalDate = self.lastModificationDate;
self.lastModificationDate = [NSDate date];
NSLog(@"Last modification date changed from %@ to %@",
originalDate, self.lastModificationDate);
在这个例子中,局部变量originalDate保持着强引用对lastModification对象。当lastmodification属性改变的时候,这个属性不能保持强引用给原始的date,可是date是继续保持存活因为originalDate强引用变量。
注意:一个变量维持着强引用给另外一个对象只要这个变量在这个范围内,或者直到他分配给另外一个对象或者nil。
若是你不想要一个变量维持强引用,你能够声明弱引用像这样:
NSObject * __weak weakVariable;
由于弱引用不能保持对象存活,有可能引用对象被销毁而后这个引用仍然被使用。为了不这个悬空的指针指向以前原始对象占用的内存。弱引用须要自动把他设置为空当对象释放的时候。
这意味着若是你用弱引用在以前的例子中:
这个originalDate的变量可能潜在的被设置为空。当self.lastModification被再次指定,这个属性不能维持强引用关系对originaldate。若是没有强引用指向他,这个原始日期将要被销毁而且originaldate被设置nil。
弱的引用多是困惑的来源像这样:
NSObject * __weak someObject = [[NSObject alloc] init];
这个例子,刚建立的对象没有强引用对他,所以很快被释放而且指针指向nil。
NSObject *cachedObject = self.someWeakProperty; // 1
if (cachedObject) { // 2
[someObject doSomethingImportantWith:cachedObject]; // 3
} // 4
cachedObject = nil; // 5
在这个例子中,强引用在第一行建立,意味着对象确保在下面两个方法调用的时候仍然存活着。在第5行这个对象被设置为nil。今后放弃了强引用,若是原始对象没有其余的强引用指向他,他就会被销毁而且指针指向空。
用Unsafe Unretained引用对一些类
有一些类在cocoa和cocoa Touch不支持弱引用。这意味着你不能定义弱引用给属性和局部变量来追踪他们,这些类包括NSTextView,NSFont,和NSColorSpace,还有不少在Transitioning to ARC Release Notes.笔记里。
若是你须要弱引用对一些类你必须用不安全的引用。对于一个属性,这意味着用Unsafe Unretained
@property (unsafe_unretained) NSObject *unsafeProperty;
对于变量,你须要这样使用_unsafe_untetained
NSObject *_unsafe_unretained unsafeReference;
这个引用有点像弱引用,他不让他的相关对象活着,可是不能设置nil若是目标对象被销毁了。这意味着你将要遗留一个悬空指针指向原来对象占用的那块内存,所以属于不安全是由于发送一个消息到悬空指针结果是崩溃的。
复制属性维持着他们的拷贝
在一些状况下一个对象也许但愿保持他的一份copy。
@interface XYZBadgeView : NSView
@property NSString *firstName;
@property NSString *lastName;
@end
例如这个类的接口声明两个属性默认是强引用的。想一想会发生什么若是另外一个对象建立一个字符串设置他的属性像这样:
NSMutableString *nameString = [NSMutableString stringWithString:@"John"];
self.badgeView.firstName = nameString;
这个完美有效的,由于NSMutableString是NSString的子类。尽管这个View认为这是处理NSString的一个实例,这意味着这个字符串对象是能够被改变的:[nameString appending:@"qiqi"];
在这个例子当中,尽管这个名字是@“john”在当时建立的时候被设置为View的firstName属性,可是如今由于改变告终果变成“johnqiqi”由于可变字符串是被改变了。
你可能选择这个View应该维持他本身的副本包括他的属性,因此他有效的被扑捉这个字符串在这个属性被设置的时候。添加两个属性他们的属性关键字是copy。
@interface XYZBadgeView : NSView
@property (copy) NSString *firstName;
@property (copy) NSString *lastName;
@end
这个View维持着他们本身的copy里面有两个字符串,即便一个可变的字符串被设置随后又发生了改变,这个View捕捉了在那时候设置的值,例如:
NSMutableString *nameString = [NSMutableString stringWithString:@"John"];
self.badgeView.firstName = nameString;
[nameString appendString:@"ny"];
这时候firstName被View拥有将是一个未受影响的copy对原始的“John”字符串。这个copy关键字属性意味着属性将要用的是强引用,由于他可能被新建立的对象所拥有。
注意:任何属性带有copy关键字的都必须支持NSCopying协议,这意味着他应该符合NSCopying协议。
若是你须要直接设置copy属性的辅助实例变量,例如一个初始化方法,不要忘了设置copy原始的对象。例如:
- (id)initWithSomeOriginalString:(NSString *)aString {
self = [super init];
if (self) {
_instanceVariableForCopyProperty = [aString copy];
}
return self;
}