Objective-C 中 @porperty 使用

1,049 阅读8分钟

1. @porperty简介

Objective-C 的属性 (property) 是通过用@property定义。例如:

@property (readonly, getter=isBlue) BOOL blue;

属性捕获了对象的状态。它们反映了对象的固有属性 (intrinsic attributes) 以及对象与其他对象之间的关系。属性(property)提供了一种安全、便捷的方式来与这些属性 (attribute) 交互,而不需要手动编写一系列的访问方法,如果需要的话可以自定义gettersetter方法来覆盖编译器自动生成的相关方法。

尽量多的使用属性(property)而不是实例变量,因为属性(property)相比实例变量有很多的好处:

  • 自动合成gettersetter方法。当声明一个属性 (property) 的时候编译器默认情况下会自动生成相关的gettersetter方法
  • 更好的声明一组方法。因为访问方法的命名约定,可以很清晰的看出gettersetter的用处。
  • 属性(property)关键词能够传递出相关行为的额外信息。属性提供了一些可能会使用的特性来进行声明,包括assign,copy,weak,strong,atomic,nonatomic,readwrite,readonly等。

2. @property用法

1. 手动定义实例变量

手动定义实例变量,以及手动创建变量的gettersetter的声明、实现:

@interface Person : NSObject {
    NSString *_name;
    NSUInteger _age;
}
//在 .h 文件声明 getter 和 setter 方法.
- (void)setName:(NSString*)name;
- (NSString*)name;
- (void)setAge:(NSUInteger)age;
- (NSUInteger)age;
@end

@implementation Person
//在 .m 文件实现 getter 和 setter 方法。
//getter 和 setter 本质就是符合一定命名规范的实例方法。
- (void)setName:(NSString*)name {
    _name = [name copy];
}
- (NSString*)name {
    return _name;
}
- (void)setAge:(NSUInteger)age {
    _age = age;
}
- (NSUInteger)age {
    return _age;
}
@end
int main(int argc, const char * argv[]) {

    Person *p = [[Person alloc] init];
    [p setName:@"XXXX"]; //p.name = @"XXX";
    [p setAge:20]; 	 //p.age = 20;
    NSLog(@"%@ %ld", [p name], [p age]);
    // NSLog(@"%@ %ld", p.name, p.age);
    //Objective-C 允许使用点语法来访问方法。
    //使用点语法访问的方式本质还是调用了我们手动创建的 setter 和 getter。
    
    return 0;
}

2. 使用@property声明属性

1. @property

使用@property声明两个属性nameage并自动生成属性的gettersetter方法的声明(Xcode4.5之前):

@interface Person : NSObject 

@property  NSString* name;
@property  NSUInteger age;

@end

@implementation Person
- (void)setName:(NSString*)name {
    _name = [name copy];
}
- (NSString*)name {
    return _name;
}
- (void)setAge:(NSUInteger)age {
    _age = age;
}
- (NSUInteger)age {
    return _age;
}
@end

2. @synthesize

@synthesize是一个编译器指令,使用@synthesize为这两个属性自动生成(编译器默认)名为_name_age的私有实例变量,(在.m文件中生成的)来存储属性,并自动生成属性的gettersetter方法的的实现, 也可以自定义gettersetter方法来覆盖编译器默认生成的方法,就如同手动创建gettersetter一样。

@interface Person : NSObject

@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) NSUInteger age;

@end

@implementation Person

@synthesize name = _name;
@synthesize age = _age;

@end
/*
@synthesize age = _age;
编译器会在编译时会自动生成并使用实例变量(_age)来存储 age 属性, _age 和 age 没什么关系, 只是在上层使用这两个属性的时候可以用 name 和 age 的点语法来访问 getter 和 setter。

如果不想自动生成并使用默认的实例变量来存储 age 属性也可以任意命名,但最好按照官方的命名原则来命名;
@synthesize age = XXXX;
编译器会在编译时会自动生成并使用 XXXX 这个实例变量来存储 age 属性

@synthesize age;
属性后面没有告诉系统将传入的值赋值给谁, 编译器会在编译时会自动生成并使用与属性同名的实例变量来存储 age 
*/

3. 使用@property(增强)声明属性

Xcode4.5之后 使用@property声明属性,自动做了三件事

  • .h : 声明属性的gettersetter方法;
  • .m : 实现属性gettersetter方法;
  • .m : 声明私有实例变量(默认:_属性名);

3. @property修饰符

使用@property声明两个属性nameage并为其设置了一些指示符.

@interface Person : NSObject

@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) NSUInteger age;

@end

在声明属性的时候一般会带上几个指示符,指示符有(前三种类型常用):

  1. Atomicity(原子性):atomic / nonatomic
  2. Access(存取特性): readwrite / readonly
  3. Storage(内存管理特性): strong / weak / assign / copy / retain
  4. getter = XXX / setter = XXX
  5. Nullability: nullable / nonnul / null_unspecified / null_resettable

1. 原子性

1. atomic

默认属性, 在一定程度上可以保证线程安全,atomic的作用只是给gettersetter加锁。即, 有线程在访问setter, 其他线程只能等待完成后才能访问。

2. nonatomic

不保证你获得的是有效值,如果像上面所述,读、写两个线程同时访问变量,有可能会给出一个无意义的垃圾值。因为atomic并不能完全保证程序层面的线程安全,又有额外的性能耗费,所以设置都会使用nonatomic,这样能够提高访问性能。atomicnonatomic的本质区别其实也就是在setter方法上的操作不同。

2. 存取特性

3. readwrite

默认属性, 编译器自动生成属性的gettersetter

4. readonly

编译器只生成属性的 setter。另外,可以在.h文件中用readonly修饰在.m文件类扩展中用readwrite修饰同一个属性,这样来防止外界篡改该属性。

3. 内存管理特性

5. strong

ARC 属性默认值, 表示属性对所赋的值持有强引用,会先保留新值即增加新值的引用计数,然后再释放旧值即减少旧值的引用计数。只能修饰对象。

6. copy

防止属性被修改。浅拷贝时,属性对所赋的值增加引用计数,深拷贝时,对所赋的值不会增加引用计数,然后再释放旧值即减少旧值的引用计数。一般用来修饰有可变类型子类的对象。如:NSString,NSArray,NSDictionary等。

对于可变对象类型,如NSMutableStringNSMutableArray等则不可以使用copy修饰。因为Foundation框架提供的这些类都实现了NSCopying协议,使用copy方法返回的都是不可变对象,所以使用:strong

copy还被用来修饰block,在 ARC 默认会用copy修饰, 一般情况下在block需要捕获外界数据时该block就会被分配在堆区,但在MRC环境下由于手动管理引用计数,block一般被分配在栈区,需要copy(转移)到堆区来防止野指针错误。

7. weak

表示属性对所赋的值对象持有弱引用,不会增加所赋的新值的引用计数,也不减少旧值的引用计数,但当该值被销毁时,weak修饰的属性会被自动赋值为nil,这样就可以避免野指针错误。只能修饰对象。

8. assign

MRC 属性默认值, 对属性只进行简单的赋值操作,不更改所赋的新值的引用计数,也不改变旧值的引用计数,常用于基础类型。也可以修饰对象,当对象被销毁时,编译器不会将该属性置为nil(产生野指针)。

9. unsafe_unretained

对属性只进行简单的赋值操作,不更改所赋的新值的引用计数,也不改变旧值的引用计数,当对象被销毁时,编译器不会将该属性置为nil(产生野指针)。只能修饰对象。

10. retain

MRC 特性。表示属性对所赋的值会先保留新值即增加新值的引用计数,然后再释放旧值即减少旧值的引用计数。只能修饰对象。

4. 存取方法重命名

11. setter

12. getter

//程序员之间有一个约定, 一般情况下获取 BOOL 类型的属性的值, 我们都会将 getter 方法名称改为: isXXX
@property ( getter = isBlue) BOOL blue;

//存取方法不能以 new 开头,如果你要以 new 开头命名一个属性,会默认生成一个 new 开头的 getter 方法:
//这时候就会报错:Property follows Cocoa naming convention for returning 'owned' objects。
//解决办法,就是用 getter = 重命名 getter 方法
@property (nonatomic, copy, getter = theNewName,setter = XXX) NSString *newName;

5. 为空性

为了更好地和Swift混编(配合Swiftoptional类型),在 iOS9Xcode 6.3Objective-C新增了一个语言特性,nullability。具体就是以下 4 个新特性。

13. nonnull

表示属性不可为空。

在接口中nullable的是少数,因此为了防止写一大堆nonnullFoundation提供了一对宏,包在宏里面的对象默认加nonnull修饰符,如果属性可以为空只需将nullable手动添加。

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject

@property (nonatomic, copy) NSString *name1;
@property (nonatomic, assign) NSUInteger age;

@property (nonatomic, copy, nullable) NSString *name2;
//等价
@property (nonatomic, copy) NSString *_Nullable name2;

@end
  
NS_ASSUME_NONNULL_END

14. nullable

表示属性可以为空。

15. null_resettable

表示属性 setter 参数值 修饰符为 nullablegetter 返回值 修饰符为 nonable. 即 , setter可以为空,getter不能为空, (比如某些属性提供了默认值,此时使用 null_resettable 修饰属性 )。需重写settergetter其中之一,自己做判断,确保真正返回的值不是nil。否则报警告:Synthesized setter 'setName:' for null_resettable property 'name' does not handle nil

16. null_unspecified

属性默认值, 表示属性不确定是否为空。