属性
日常开发中,我们可以利用@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
,这就会产生野指针,所以是不安全的。