Objective 属性的属性properties' attribute

前面翻译过Rypress写的关于Objective-C属性properties的文章,文章只翻译了一半,这次继续。
上次提到@properties可以指定类属性的getter和setter。我们可以通过设定gettter、setter在实现文件中通过指定的写方法实现setter、getter。

其实,properties的属性不仅仅是有setter、getter。除此之外,我们还会见到以下的一些属性attributes:

1
atomic, nonatomic, readonly, readwrite, assign, retain, copy, getter, setter

下面,我们将重点看一下properties的attributes。

readyonly属性

只读属性可以很方便的将properties设置成为只读。这货将setter方法禁用了,并且对点操作符(.)做了保护(不能通过.操作符来设定属性的值),但是getter方法不受影响。举个例子(注意,当属性properties有多个attribute时,中间的分隔符是英文状态的 ,

1
2
3
4
5
6
7
// 文件Car.h
#import "Foundation/Foundation.h";
@interface Car : NSObject
@property (getter = isRunning, readonly) BOOL running;
- (void)startEngine;
- (void)stopEngine;
@end

这里我们定义了两个方法来操控running的取值startEngine、stopEngine。接下来,我们来实现它们:

1
2
3
4
5
6
7
8
9
10
11
// 文件Car.m
#import "Car.h"
@implemention Car
- (void)startEngine {
_running = YES;
}

- (void)stopEngine {
_running = NO;
}
@end

需要记住的是,@properties会自动给我们生成一个实例变量,这样下来我们就可以通过下划线的方式来访问属性了(Notice不要尝试使用self.running这种形式的动作来访问了,因为running现在是readonly的呢)。

接下来,我们就开一下我们的新车吧!

1
2
3
4
5
6
7
// Codes in main.m

Car *bmw = [[Car alloc] init];
[bmw startEngine];
NSLog(@"Running: %d", bmw.running);

// 注意:不要尝试了bmw.running = NO;running是个只读属性!

到此为止,我们了解了属性的一些便捷操作,我们知道了这家伙可以自动为我们创建类似于setter和getter的方法。然并卵,它却不是属性properties的唯一属性attribute!并且这样定义的setter和getter只适用于对象类型的数据,在面对C的数据结构的时候,它就不那么好使了!

原子性

原子性(atomicity)是定义属性properties在线程当中的表现形式。当你有多于一个的线程的时候,程序可能会在同一时间调用了setter或者getter方法。也就是说,我们的setter和getter可能会被其它线程破环掉,如此一来,我们的程序很可能会驾崩~~~

原子性可以给我们的属性properties上锁,上锁之后的属性就可以不被其它线程破坏,从而使我们可以畅快的存取属性值!可惜,蛋疼的是,使用了原子性的属性并非是觉得的线程安全的!我们在敲代码的时候还得兼顾其它细节!

默认情况下,属性properties就是atomic的,即原子性的。这样下来,如果你的程序并非多线程的,或者你自有一套线程安全管理机制,那么你就需要重新定义属性的原子性了!你的代码可能看起来像下面这样:

1
@property (nonatomic) NSString *model;

需要注意的一个细节是:原子的访问器必须存在(它可以是用户定义,也可以是代码生成)。只有非原子性的属性才可以让你自定义合成访问器!你可以通过移除上面代码当中的nonatomic之后添加自定义的getter方法。

内存管理

在任何面向对象的编程语言当中,我们总是将对象存储在内存中。我们知道对于设备,内存是个很稀缺的玩意儿,尤其在移动设备中,内存显得异常珍贵!所以,一个好的内存管理机制,很有必要!在很多语言当中,对于内存的管理有一套垃圾回收机制,但是Objective-C却是一套谁创建谁销毁的机制!这样一来,当你申请了内存之后,你就拥有了那块内存,直到你销毁它之前,它就一直属于你!并且,在你完成工作之后,如果不再使用该内存,并且没有其它的成员使用该内存,你就需要手动释放它!

别忘了销毁不用的内存

所幸,Objective-C有一套自动引用计数技术,编译器会管理你对象的所有权。大多数情况下,你是无需担心内存管理的实际工作情况!但是,你需要明白的是,strong, weak, copy这三个attribute会告知编辑器使用什么方式来管理内存。

strong属性

当我们把属性变量声明称为strong时,你就有了这个变量的控制权!如果有多方访问这个变量,那么只有它们全部不再操作时,这个变量才会销毁(release)。这样一来,线程就是安全的了!

接下来我们看看strong是如何工作的!

1
2
3
4
5
// Person.h
#import "Foundation/Foundation.h";
@interface Person : NSObject
@property (nonatomic) NSString *name;
@end

实现文件如下。代码中我们使用了默认由property生成的访问器。代码重写了NSObject的description方法。

1
2
3
4
5
6
7
// Person.m
#import "Person.h"
@implemention Person
- (NSString *)description {
return self.name;
}
@end

接下来,我们在Car类中定义一个Person属性。新的Car.h代码如下:

1
2
3
4
5
6
7
// Car.h
#import "Foundation/Foundation.h";
#import "Person.h"
@interface Car : NSObject
@property (nonatomic) NSString *model;
@property (nonatomic, strong) Person *driver;
@end

在Person.m文件中实现它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import "Foundation/Foundation.h";
#import "Car.h"
#import "Person.h"

int main(int argc, const char* argv[]) {
@autoreleasepool {
Person *httpbin = [[Person alloc] init];
httpbin.name = @"HttpBin";

Car *bmw = [[Car alloc] init];
bmw.model = @"BMW 760Li";
bmw.driver = httpbin;

NSLog(@"%@ is driving the %@", bmw.driver, bmw.model);
}
}

因为我们在声明属性Person的时候指定了strong,所以httpbin可以在car中使用。

weak属性

大多数情况下strong属性能解决我们的问题!但是有些情况是strong却存在问题。比如,我们现在有个需求是在Person类中知晓Car的信息。代码如下:

1
2
3
4
5
6
7
// Person.h
#import <Foundation/Foundation.h>
@class Car
@interface Person : NSObject
@property (nonatomic) NSString *name;
@property (nonatomic, strong) Car *car;
@end

@class Car这一行表示在类中声明了Car类。意思就是高速编译器,我需要引用Car类,并且Car类存在。我们之所以不使用import表达式,是因为Car类中已经存在了Person类的import。如果我们继续采用import方式,编译器会出问题!

接下来,在main.m中增加如下代码。

1
2
bmw.driver = httpbin;
httpbin.car = bmw; //添加的行

这样下来我们就有了如图的对象引用关系,如此,我们就形成了你中有我,我中有你的局面!好吧,这样下去,内存管理系统就无法销毁任何一个不再使用的对象了!

环抱的内存引用图

这种方式叫做循环引用(retain cycle),这样的下场是导致内存泄漏!要解决这个问题也很方便!将被任何一方被引用的属性设置成为weak即可!我们修改Person.h中的代码如下:

@property (nonatomic, weak) Car *car;

weak属性定义的属性可以防止死循环。但是这样下来,即便bmw销毁httpbin还是会存在引用。还在weak属性会将car设置成为nil来防止空指针!

我们可以采用图示如下:
weak reference

weak属性在父子关系结构的数据中比较常见。约定为,在父类中定义个strong的属性供子类引用,而在子类中维护一个weak属性来调用父类属性。在代理模式当中,weak引用也是很要的一环。

内存释放的前提是,两个对象中没有任何一个引用存在!而weak的存在则可以让对象周期性的存在,而不去创建一套周期的管理机制。

copy属性

copy是strong的替代属性。但是它采用的是不一样的内存管理机制!即,它直接将属性拷贝了一份!只有符合NSCopying协议(protocol)才可以使用这个属性。
值类型的属性比较适合复制!例如,我们需要将NSString强制引用!

Person.h
1
@property (nonatomic, copy) NSString *model

这样,我们就可以给car储存一个新的饿model实例。当我们使用mutable类型的值的时候,我们就可以将分配的值冻结。再来一份代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#import "Foundation/Foundation.h";
#import "Car.h"

int main(int argc, const char * argv[]) {
@autoreleasepool {
Car *bmw = [[Car alloc] init];
NSMutableString *model = [NSMutableString stringWithString:@"bmw 760Li"];
bmw.model = model;
NSLog(@"%@", bmw.model);
[model setString:@"bmw X6"];
NSLog(@"%@", bmw.model); // 扔人是"bmw 760Li"
}
return 0;
}

NSMutableString是NSString的子类,如果model没有创建一份原始的实例,那么我们可以看到它被更改了。