实例变量和属性
声明
Person 文件中 @interface Person : NSObject { NSString *_name; //实例变量 } @property(copy) NSString *firstName; //属性 @property(copy) NSString *lastName; @end
_name 是实例变量,实例变量是类私有的变量,其他类对象无法直接访问;
写在头文件中的 firstName是属性,public,其他类对象可以直接访问;写在m文件中的属性是private,其他类对象无法直接访问。
赋值
在初始化方法中,应该直接访问实例变量,代码如下
- (id)initWithFirstName:(NSString *)aFirstName lastName:(NSString *)aLastName { self = [super init]; if (self) { _firstName = aFirstName; // 直接访问 _lastName = aLastName; } return self; }
访问:
存取方法
初始化时,应该直接访问实例变量,因为在那个时候,实例变量可能还没有被正确赋值。如果通过存取方法访问,会引起问题。
存取方法实例:
// 存方法
-(void)setName:(NNString *)name { _name=name; }
// 取方法
-(NNString *)name { return _name; }
声明一个属性,等于隐式地为响应名称的实例变量声明一对存取方法,更加简便。也就说在头文件写在这一行代码
@property NNString *name
编译器会自动生成实例变量_name,取方法name,和存方法setName。
当默认的存取方法无法满足需求怎么办?比如,某个在赋值后还需要其他操作,则需要自定义属性的存取方法。
-(void)setName:(NNString *)name { _name=name; // 其他操作 ``` }
但是此时,编译器就不会为name属性创建存方法,不过仍然会创建取方法。如果我们同时覆盖了存取方法(或者只读属性覆盖了取方法),那么编译器就不会自动创建相应的实例变量_name。有些时候,我们不需要编译器为属性默认生成实例变量,可以同时覆盖属性的读取方法.
比如,有个需求:当某个属性第一次访问的时候,才对它进行初始化:
懒访问
- (XYZObject *)someImportantObject { if (!_someImportantObject) { _someImportantObject = [[XYZObject alloc] init]; // 直接访问实例变量 } return _someImportantObject; }
合成
其实,在头文件声明属性时,只会生成存取方法声明,为了让属性生成实例变量并实现存取方法,属性必须被合成(synthesized).而通常情况下,编译器会自动合成属性并生成默认的实例变量和存取方法。 我们可以在文件中用@synthesize指令自定义属性合成方式
@synthesize name = _name;
这行代码和编译器自动合成的效果相同,左边的name表示创建存取方法setName和name,右边的name 表示创建name实例变量。
点句法
运行时和存取方法运行时是没有区别的,它们都会调用之前实现的存取方法。但是点句法的可读性更好,Apple官方也坚持使用点句法存取实例变量。
NSString *firstName = somePerson.firstName;
somePerson.firstName = @"Johnny";
- NSString *firstName = somePerson.firstName 相当于[somePerson firstName]
- somePerson.firstName = @"Johnny"相当于[ somePerson setFirstName:@"Johnny"];
注意:用点句法操作只读属性,会报编译错误。
关系
实例变量,在对象(object)的生命周期中会一直存在,它所占用的内存是在对象首次创建(alloc)的时候被分配的;而对象销毁时,它占用的内存会被释放。默认情况下,属性被编译器自动合成的时候,会生成相应实例变量和读取方法。
Apple 官方推荐使用property。
属性的特性
任何属性都有三个特性,每个特性有多种类型,用于描述相关存取方法的行为。这三种特性分别是多线程、读写特性、内存管理特性。
多线程特性
多线程特性有两种选择类型,atomic和nonatomic。默认是atomic。这意味着,访问必须是原子操作,不能被其他操作打断。比如,声明一个属性
@property NSObject *implicitAtomicObject;
即使从两个不同的线程同时请求访问implicitAtomicObject(一个存一个取),每次访问(存或取)不能被打断,先存完再取,或者先取完再存。
读写属性
读写属性也有两个特性,readwrite和readonly。编译器会为readwrite特性的属性生成存取方法,如果只有readonly特性,只为该属性生成取方法
内存管理
内存管理属性有四个特性,strong,weak,copy,unsafe_unretained .默认strong.
unsafe_unretained:
对于不需要做内存管理的属性,比如int objProperty 不指向任何对象,不需要做内存管理,可以直接用unsafe_unretained ,它表示存取方法会直接为实例变量赋值。
@property (unsafe_unretained) int objProperty
unsafe_unretained类型的指针指向的对象被销毁时,指针不会自动设为nil,而是成为空指针,因此不安全。但是当处理非对象属性时,是不会出现空指针问题的。
copy
当某个属性是指向其他对象的指针,而且该对象的类有可修改的子类,(比如NSString/NSMutableString)应该将该属性的内存管理特性设置为copy。 比如firstName属性,用了copy属性后,存方法改类似以下:
-(void)setFirstName:(NNString *)firstName
{
_firstName=[firstName copy ];
}
至于所有权问题:copy方法返回的是拥有强引用特性的指针,而收到copy消息的NSString对象不会发生任何 变化;该对象不会获得也不会失去拥有者。
只有可变对象应该设置为copy,而复制不可变对象会浪费内存空间。为了避免不必要的复制,向不可变对象发送copy消息时,会返回指向不可变对象自己的指针。