想起夕阳下的奔跑,那是我逝去的青春

0%

iOS关于属性的讲解

属性

日常开发中,我们可以利用@property来创建一个属性,而该属性包含了一个成员变量,并且自动生成了get和set方法,也可以这样来解释:

1
@property属性 = setMethod + getMethod + 成员变量var

当我们声明一个属性username的时候,在编译阶段编译器会自动给对象添加一个成员变量_username(注意会自动生成下划线,下一段落会提到这一点)以及赋予其方法- (void)setUsername:(NSString *)username- (NSString *)username

由于这个过程是编译阶段自动合成的,所以对于我们来说是隐藏了这步骤,而且添加成员变量也是有前提的,比如@property (nonatomic, strong) NSString *username,定义该属性之后,会自动生成_username带有下划线的成员变量,我们也是可以在代码中直接使用该成员变量,如果在同一时期,我们也写入了_username这个样的变量,那么久重复了,没有意义了。
这里将给出示例,请注意里面注释的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#import "TestController.h"
#include <objc/runtime.h>

@interface TestController () {
NSString *age;
// NSString *_username;
}

@property (nonatomic, strong) NSString *username;

- (void)getPhone;

@end

@implementation TestController

- (void)getPhone {
NSLog(@"this user phone is 11012010086");
}

- (void)noHeaderMethod {
NSLog(@"this is no header method");
}

- (void)viewDidLoad {
[super viewDidLoad];

self.view.backgroundColor = [UIColor whiteColor];

unsigned int count = 0;
Ivar *vars = class_copyIvarList([self class], &count);
for(unsigned int i = 0; i < count; i++) {
const char *var_name = ivar_getName(vars[i]);
NSLog(@"成员变量名称------%s\n", var_name);
}

Method *methods = class_copyMethodList([self class], &count);
for(unsigned int i = 0; i < count; i++) {
SEL method_name = method_getName(methods[i]);
NSLog(@"方法名-----%@", NSStringFromSelector(method_name));
}
}

@end

2019-12-03 14:44:40.195125+0800 DZWebLocalDemo[1532:33451] 成员变量名称------age

2019-12-03 14:44:40.195274+0800 DZWebLocalDemo[1532:33451] 成员变量名称------_username

2019-12-03 14:44:40.195377+0800 DZWebLocalDemo[1532:33451] 方法名-----getPhone
2019-12-03 14:44:40.195459+0800 DZWebLocalDemo[1532:33451] 方法名-----noHeaderMethod
2019-12-03 14:44:40.195520+0800 DZWebLocalDemo[1532:33451] 方法名-----.cxx_destruct
2019-12-03 14:44:40.195595+0800 DZWebLocalDemo[1532:33451] 方法名-----setUsername:
2019-12-03 14:44:40.195659+0800 DZWebLocalDemo[1532:33451] 方法名-----username
2019-12-03 14:44:40.195746+0800 DZWebLocalDemo[1532:33451] 方法名-----viewDidLoad

接下来,我们将放开上面注释的代码,看一下的打印输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#import "TestController.h"
#include <objc/runtime.h>

@interface TestController () {
NSString *age;
NSString *_username;
}

@property (nonatomic, strong) NSString *username;

- (void)getPhone;

@end

@implementation TestController

- (void)getPhone {
NSLog(@"this user phone is 11012010086");
}

- (void)noHeaderMethod {
NSLog(@"this is no header method");
}

- (void)viewDidLoad {
[super viewDidLoad];

self.view.backgroundColor = [UIColor whiteColor];

unsigned int count = 0;
Ivar *vars = class_copyIvarList([self class], &count);
for(unsigned int i = 0; i < count; i++) {
const char *var_name = ivar_getName(vars[i]);
NSLog(@"成员变量名称------%s\n", var_name);
}

Method *methods = class_copyMethodList([self class], &count);
for(unsigned int i = 0; i < count; i++) {
SEL method_name = method_getName(methods[i]);
NSLog(@"方法名-----%@", NSStringFromSelector(method_name));
}
}

@end

2019-12-03 14:45:38.439313+0800 DZWebLocalDemo[1554:34308] 成员变量名称------age

2019-12-03 14:45:38.439435+0800 DZWebLocalDemo[1554:34308] 成员变量名称------_username

2019-12-03 14:45:38.439546+0800 DZWebLocalDemo[1554:34308] 方法名-----getPhone
2019-12-03 14:45:38.439617+0800 DZWebLocalDemo[1554:34308] 方法名-----noHeaderMethod
2019-12-03 14:45:38.439678+0800 DZWebLocalDemo[1554:34308] 方法名-----.cxx_destruct
2019-12-03 14:45:38.439832+0800 DZWebLocalDemo[1554:34308] 方法名-----setUsername:
2019-12-03 14:45:38.440014+0800 DZWebLocalDemo[1554:34308] 方法名-----username
2019-12-03 14:45:38.440082+0800 DZWebLocalDemo[1554:34308] 方法名-----viewDidLoad

请大家对比以上,打印输出除了时间外没有任何的区别。。。。。。
当然,对于带有@property的属性,我们也可以重新去定义它的set方法和get方法,这样一来,达到满足自己的编程需求。

@synthesize关键字

一直认为,@synthesize关键字,在MRC模式下使用的更多,而目前的开发我们都是用ARC模式,很少见@synthesize,其主要有2个作用:

  • 在MRC下,@synthesize username这样,编译器才会自动合成str的存取方法。不过在ARC下就不必了,无论你是否@synthesize username,编译器都会自动合成str的存取方法
  • 你的属性是username,系统会自动给你添加待下划线的成员变量是_username,如果你想使用其他的成员变量替代,可以这样写username = replaec_username,后面的名称是自定义的。 replaec_username,而不是 username,但是这样的意义并不大

readwrite,readonly关键字

我们定义的属性,一般默认为readwrite,但是有时候,我们希望能够暴露给外部的属性为可读属性,那么我们可以在.h头文件写入以下的关键字@property (nonatomic, strong, readonly) NSString *username;,然后这样又会引起另外一个问题,就是在.m实现文件里,无法写入值,那么需要在这里再次声明一个属性不过换了一个关键字@property (nonatomic, strong, readwrite) NSString *username;,这样就可以实现在.h头文件外部只可读取,而内部.m执行文件可读可写。

nonatomic,atomic原子性

在默认情况下,由编译器所合成的方法会通过锁定机制确保其原子性atomic。如果属性具备nonatomic,则不需要同步锁。具备atomic特质的获取方法会通过锁定机制来确保其操作的原子性。
一般iOS程序中,所有属性都声明为nonatomic。这样做的原因是:
在iOS中使用同步锁的开销比较大, 这会带来性能问题。一般情况下并不要求属性必须是“原子的”,因为这并不能保证“线程安全”(thread safety),若要实现“线程安全”的操作,还需采用更为深层的锁定机制才醒。可以这样概括:

  • atomic:原子属性,为setter方法加锁,系统默认使用atomic,线程安全,需要消耗大量资源
  • nonatomic:非原子属性,不会为setter方法加锁,非线程安全,适合内存小的移动设备

strong,weak,assign,copy,unsafe_unretained关键字

这些关键字仅会影响属性的set方法,编译器根据不同的关键字生成不同的代码。当我们自定义set方法的时候,也应该符合属性所具备的特质。除了assign可以用来修饰基本数据类型外,其他的关键字都只能用来修饰对象。
   strong表示一种“拥有关系”。为属性设置新值的时候,设置方法会先保留新值(新值的引用计数加一),并释放旧值(旧值的引用计数减一),然后将新值赋值上去。相当于MRC下的retain。
   weak表示一种“非拥有关系”。用weak修饰属性的时候,为属性设置新值的时候,设置方法既不会保留新值(新值的引用计数加一),也不会释放旧值(旧值的引用计数减一)。当属性所指的对象释放的时候,属性也会被置为nil。用于修饰UI控件,代理(delegate)。
   assign可以同时用来修饰基本数据NSInteger,CGFloat等类型和对象。当assign用来修饰对象的时候,和weak类似。唯一的区别就是当属性所指的对象释放的时候,属性不会被置为nil,这就会产生野指针。
   copy修饰的属性设置新值的时候,当新值是不可变的,和strong是一模一样的。当新值是可变类型Mutable,设置方法不会保留新值(新值的引用计数加一),而是对新值copy一份,不会影响新值的引用计数。copy常用来修饰NSString,因为当新值是可变的,防止属性在不知不觉中被修改。
  unsafe_unretained用来修饰属性的时候,和assign修饰对象的时候是一模一样的。为属性设置新值的时候,设置方法既不会保留新值(新值的引用计数加一),也不会释放旧值(旧值的引用计数减一)。唯一的区别就是当属性所指的对象释放的时候,属性不会被置为nil,这就会产生野指针,所以是不安全的。