Jewelz`s Home Page

iOS工程师一枚,热爱前端技术

0%

属性 -- 你真的弄明白了吗?

“属性”(property)是 Objective-C 2.0 的一项特性,用于封装对象中的数据。Objective-C 对象通常会把其所需要的数据保存为实例变量,并通过 “存取方法”(gettersetter) 来访问。getter用于读取变量值,setter用于给属性设置值。”属性”这一特性的引入,使得这一切可以让编译器自动完成。此特性还引入了一种新的”点语法”,使开发者可以更容易地依照类对象来访问数据。

自动合成

在 Objective-C 中,你可以在类的接口定义中使用 @property 语法就能定义一个 “属性”。其意思是说:编译器会自动生成存取方法,并向类中添加相应的实例变量。例如下面的例子:

1
2
3
4
5
@interface HUPerson : NSObject

@property (nonatomic, copy) NSString *name;

@end

这跟以前不使用 “属性” 写出来的代码是等效的:

1
2
3
4
5
6
7
8
@interface HUPerson : NSObject {
NSString *_name;
}

- (NSString *)name;
- (void)setName:(NSString *)name;

@end

访问属性时,我们可以使用 “点语法”,编译器会把 “点语法”转换为相应的存取方法的调用,使用”点语法”的效果与直接调用存取方法相同。

1
2
3
4
5
6
7
8
HUPerson *person = [[HUPerson alloc] init];

// 下面的调用是一样的
person.name = @"jewelz";
[person setName:@"jewelz"];

//NSString *name = person.name;
NSString *name = [person name];

使用属性时,编译器自动生成存取方法和实例变量的过程叫做 “自动合成”(autosynthesis)。这个过程在由编译器在编译期执行,所以我们在编译器里看不到这些自动合成的代码。自动合成的示例变量名是在属性名前面加上下划线。不过我们可以使用@synthesize语法来指定示例变量名:

1
2
3
4
@implementation HUPerson
@synthesize name = _aName;

@end

上面的代码会将生成的实例变量命名为_aName,而不再使用默认的名称。不过在项目中使用这个方法修改实例变量的名称并不是个好主意。统一的代码风格对于一个大型项目来说是很有必要的,所以还是使用默认的命名吧,除非你坚持要这么做。

若不想让编译器自动合成存取方法,则可以自己实现。如果你只实现了存取方法中的一个,那么另一个会由编译器帮你合成。前提是你定义的属性是可以读写的,而不是 readonly的。我们还有一种方式来阻止编译器自动合成存取方法,那就是使用 @dynamic 关键字,它会告诉编译器:不用自动给我合成存取方法也不用生成实例变量。在编译访问属性的代码时,即使编译器找不到定义的存取方法,也不会报错。例如下面这样:

1
2
3
4
5
6
7
8
9
10
@interface HUPerson : NSObject
@property (nonatomic, copy) NSString *name;

@end

@implementation HUPerson
@dynamic name;

@end

这时编译器不会为 HUPerson 自动合成存取方法或实例变量。你仍然可以正常的访问其中的属性,编译器也不会发出警告信息。如果你没有实现相应的存取方法,那么在运行时程序就会崩溃掉。

属性特性

在定义属性时,我们常常会给属性指定一些特性,例如:

1
@property (nonatomic, readwrite, copy) NSString *name;

属性的特性有四中类型:

原子性

在默认情况下,由编译器合成的方法会通过锁机制确保其原子性。如果属性指定为 nonatomic,则不会使用同步锁。系统默认是 atomic的,不过这在一定程度上会影响系统性能,所以在iOS开发中一般指定为 nonatomic的。

读写权限

  • 具备 readwrite(读写) 特性的属性同时拥有 gettersetter方法,这是编译器自动帮我们生成的。

  • 具备 readonly(只读) 特性的属性只有 getter 方法。这样的话我们就不能在类内部使用点语法来给变量赋值了,所以一般的做法是:在类的接口中对外公开为只读属性,然后在类的 Extension 中将其重新定义为读写属性。

    内存管理语义

    编译器在合成存取方法时,要根据次特性来决定使用哪种内存管理策略,在合成设置方法时,编辑器会生成相应的代码。主要有以下几种:

  • asign 设置方法只会执行针对纯类型(NSInter、CGFloat等)的简单赋值。

  • strong 此特性表明该属性定义了一种 “拥有关系”。为这种属性设置值时,设置方法会先保留新值,并释放旧值,然后再将新值设置上去。

  • weak 此特性表明该属性定义了一种 “非拥有关系”。为这种属性设置值时,设置方法既不保留新值,也不释放旧值,此特性与 asign 类似,不过在属性所指的对象遭到销毁时,属性值会被设置为 nil

  • unsafe_unretained 此特性的语言与 asign 相同,但是它使用于对象类型。它表明了一种 “非拥有关系”,不过与 weak 不同,在属性所指的对象遭到销毁时,属性值不会被设置为 nil

  • copy 此特性表达的所属关系与 strong 类似。为这种属性设置值时,并不保留新值,而是将其 “拷贝”。

    方法名

    可以通过如下特性来指定存取方法的方法名:

  • getter=name 指定 getter的名称。一个好的实践是,当某个属性是 BOOL 类型时,我们将其获取方法加上 “is” 前缀。类似下面这样:

    (nonatomic, assign, getter
    1

  • setter=name 指定 setter的名称。这种用法不太常见。