1. @property 和@synthesizer
在objective-c 1.0中,我们为interface同时声明了属性和底层实例变量,那时,属性是oc语言的一个新的机制,并且要求你必须声明与之对应的实例变量,例如:
@interface MyViewController :UIViewController
{
UIButton *myButton;
}
@property (nonatomic, retain) UIButton *myButton;
@end
在objective-c 2.0中,@property它将自动创建一个以下划线开头的实例变量。因此,在这个版本中,我们不再为interface声明实例变量。变成我们常见的形式
@interface MyViewController :UIViewController
@property (nonatomic, retain) UIButton *myButton;
@end
在MyViewController.m文件中,编译器也会自动的生成一个实例变量_myButton。那么在.m文件中可以直接的使用_myButton实例变量,也可以通过属性self.myButton.都是一样的。
注意这里的self.myButton其实是调用的myButton属性的getter/setter方法。这与C++中点的使用是有区别的,C++中的点可以直接访问成员变量(也就是实例变量)。
例如在oc的.h文件中有如下代码
@interface MyViewController :UIViewController
{
NSString *name;
}
.m文件中,self.name 这样的表达式是错误的。xcode会提示你使用->,改成self->name就可以了。因为oc中点表达式是表示调用方法,而上面的代码中没有name这个方法。所以在oc中点表达式其实就是调用对象的setter和getter方法的一种快捷方式。
你可能还见过这种写法
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) UIButton *myButton;
@end
@implementation ViewController
@synthesize myButton;
@synthesize 语句只能被用在 @implementation 代码段中,@synthesize的作用就是让编译器为你自动生成setter与getter方法,@synthesize 还有一个作用,可以指定与属性对应的实例变量,例如@synthesize myButton = xxx;那么self.myButton其实是操作的实例变量xxx,而不是_myButton了。
如果.m文件中写了@synthesize myButton;那么生成的实例变量就是myButton;如果没写@synthesize myButton;那么生成的实例变量就是_myButton。所以跟以前的用法还是有点细微的区别。
2. 类别中的属性property
类与类别中添加的属性要区分开来,因为类别中只能添加方法,不能添加实例变量。经常会在ios的代码中看到在类别中添加属性,这种情况下,是不会自动生成实例变量的。比如在:UINavigationController.h文件中会对UIViewController类进行扩展
@interface UIViewController (UINavigationControllerItem)
@property(nonatomic,readonly,retain) UINavigationItem *navigationItem;
@property(nonatomic) BOOL hidesBottomBarWhenPushed;
@property(nonatomic,readonly,retain) UINavigationController *navigationController;
@end
这里添加的属性,不会自动生成实例变量,这里添加的属性其实是添加的getter与setter方法。注意一点,匿名类别(匿名扩展)是可以添加实例变量的,非匿名类别是不能添加实例变量的,只能添加方法,或者属性(其实也是方法),常用的扩展是在.m文件中声明私有属性和方法。 Category理论上不能添加变量,但是可以使用rRuntime机制来弥补这种不足。
#import
static const void * externVariableKey =&externVariableKey;
@implementation NSObject (Category)
@dynamic variable;
- (id) variable
{
return objc_getAssociatedObject(self, externVariableKey);
}
- (void)setVariable:(id) variable
{
objc_setAssociatedObject(self, externVariableKey, variable, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
3. @private、@protect、@public
- @protected是受保护的,只能在本类及其子类中访问,在{}声明的变量默认是@protect
- @private是私有的,只能在本类访问
- @public公开的,可以被在任何地方访问。
在头文件.h中:
@interface ViewController : UIViewController
{
// 成员变量
@public
NSString* publicString;
@protected
NSString* protectedString;
@private
NSString* privateString;
}
//属性变量
@property (nonatomic,strong) NSArray *propertyString;
@end
- 成员变量用于类内部,无需与外界接触的变量。
- 根据成员变量的私有性,为了方便访问,所以就有了属性变量。属性变量是用于与其他对象交互的变量。(属性变量的好处就是允许让其他对象访问到该变量。当然,你可以设置只读或者可写等,设置方法也可自定义。)
编程习惯:
1.如果只是单纯的private变量,最好声明在implementation里.
2.如果是类的public属性,就用property写在.h文件里
3.如果自己内部需要setter和getter来实现一些东西,就在.m文件的类目里用property来声明
- .h中的interface的大括号{}之间的实例(成员)变量,.m中可以直接使用;
- .h中的property(属性)变量,.m中需要使用self.propertyVariable的方式使用propertyVariable变量
4. 成员变量和成员属性的关系
- 属性对成员变量扩充了存取方法.
- 属性默认会生成带下划线的成员变量.
- 但只声明了变量,是不会有属性的.
5. 细说 @property
- 原子性--- nonatomic 特质
- 读/写权限---readwrite(读写)、readonly (只读)
- 内存管理语义---assign、strong、 weak、unsafe_unretained、copy
- 方法名---getter=<name> 、setter=<name>
eg:@property (nonatomic, getter=isAllow) BOOL allow;
使用atomic一定是线程安全的吗?
不是的
- atomic原子操作,系统会为setter方法加锁。(但是也不安全,需要更深层次的锁定)
- nonatomic不会为setter方法加锁。
- atomic:非线程安全,还是需要消耗大量系统资源来为属性加锁
- nonatomic:非线程安全,适合内存较小的移动设备
引用计数内存管理的思考方式
- 自己生成的对象,自己所持有。
- 非自己生成的对象,自己也能持有。
- 自己持有的对象不再需要时释放。
- 非自己持有的对象无法释放
strong retain
在ARC下,实例变量本身是强引用,当ARC将传入值赋给实例变量时,它会保留传入的值,释放现有实例变量的值。非ARC下,setter方法会保留传入的值和释放现有实例变量的值。strong,retain是同义词。
copy
如果是不可变的值,行为与strong相同。
如果是可变的值,会将一个副本赋给实例变量。当一个不可变类有一个可变的子类时(NSString NSMutableString,NSArray NSMutableArray)可以防止setter 方法传递一个可变的子类的对象。会导致我们在不知情的情况下修改对象的值。
weak |
weak
- 带__weak 修饰符的变量不持有对象,所以在超出其变量作用域时,对象被释放。
可以这样理解,__weak修饰的变量没有对象的所有权,不增加对象的引用计数。
什么情况使用 weak 关键字?
-
在 ARC 中,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak 来解决,
比如: delegate 代理属性 -
自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,
自定义 IBOutlet 控件属性一般也使用 weak;当然,也可以使用strong。
assign
- 适用于值类型。而weak必须用于对象。
- assign 和 weak 的区别
assign适用于基本数据类型,weak是适用于NSObject对象,并且是一个弱引用。
assign其实也可以用来修饰对象,那么我们为什么不用它呢?因为被assign修饰的对象在释放之后,指针的地址还是存在的,也就是说指针并
没有被置为nil。如果在后续的内存分配中,刚好分到了这块地址,程序就会崩溃掉。而weak修饰的对象在释放之后,指针地址会被置为nil。所以现在一般弱引用就是用weak。
synthesize和@dynamic
@property有两个对应的词,一个是 @synthesize,一个是 @dynamic。
如果 @synthesize和 @dynamic都没写,那么默认的就是@syntheszie var = _var;
@synthesize 的语义是如果你没有手动实现 setter 方法和 getter 方法,那么编译器会自动为你加上这两个方法。
@dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。(当然对于 readonly 的属性只需提供 getter 即可)。
假如一个属性被声明为 @dynamic var,然后你没有提供 @setter方法和 @getter 方法,编译的时候没问题,但是当程序运行到 instance.var = someVar,由于缺 setter 方法会导致程序崩溃;或者当运行到 someVar = var 时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
@synthesize 合成实例变量的规则,有以下几点:
- 如果指定了成员变量的名称,会生成一个指定的名称的成员变量,
- 如果这个成员已经存在了就不再生成了.
- 如果是 @synthesize xjy; 还会生成一个名称为xjy的成员变量,也就是说:如果没有指定成员变量的名称会自动生成一个属性同名的成员变量,
- 如果是 @synthesize xjy = _xjy; 就不会生成成员变量了.
什么时候不会自动合成
- 同时重写了 setter 和 getter 时
- 重写了只读属性的 getter 时
- 使用了 @dynamic 时
- 在@protocol中定义的所有属性
- 在category中定义的所有属性
- 重载的属性 (子类中重载父类的属性)
@interface MyLabel ()
@property (nonatomic, strong) NSString *text;
@end
@implementation MyLabel
//必须用synthesize 手动指定ivar的名字就可以避免重载
@synthesize text = _myText;
@end
6. 示例
在.h
文件中添加这三个属性
@interface ClassA : NSObject
{
int _num1;
@private int _num2;
@public int _num3;
}
@end
然后在main
函数中使用这个实例对象的时候
发现只有_num3
是可以访问的,其他两个都不能访问,_num2
是手动设置为private
属性,很显然是不能访问,_num1
什么也没有设置,然而为什么也不能访问
我们来尝试给_num1
赋值,看看是什么个情况
系统给出了很详细的解释,说
_num1
是protected
,也就是说这里声明的属性默认是protected
- 手动实现
_num2
和_num3
的 setter 和 getter 方法
.h文件
- (void)setNum2:(int)num2;
- (int)num2;
- (void)setNum3:(int)num3;
- (int)num3;
.m文件
- (void)setNum3:(int)num3
{
_num3 = num3;
}
- (int)num3
{
return _num3;
}
- (void)setNum2:(int)num2
{
_num2 = num2;
}
- (int)num2
{
return _num2;
}
这样就可以实现对这两个属性的访问了
ca.num2 = 2;
ca.num3 = 2;
很显然对每个属性都去实现 setter 和 getter 方法是很费时的,这个时候@Property
就出来帮我们解决这个事了
在.h
中添加 @property int num1;
这么一句话之后,就可以实现对_num1
的访问了,因为@property
已经自动为属性num1
声明了 setter 和 getter 方法,但是为什么就直接可以访问到_num1
了?
我们来看这一句@synthesize num1 = _num1;
看到了系统默认生成的 setter 和 getter 方法,然后不管点击哪一个进入都会指向这一句话,因为我们没有在.m
文件中手动实现num1
的 setter 和 getter 方法,所以就会指向这里,可以发现@synthesize
的作用就是告诉你如果没有手动实现setter和getter方法,编译器会自动帮你生成
@property 生成的变量并不是 public 类型的,因为生成了 getter 和 setter 方法,在类外部访问的是该变量的 setter 和 getter 方法。
7. 细说property的内存管理
atomic(原子性) vs nonatomic(非原子性)【线程安全】
这主要是针对与多线程编程环境下来说的。怎么说那,假设我们现在多线程环境编程中,如果同时又两个或者两个以上的线程同时对一个属性就行赋值,这个时候为了保证属性调用前后的一致性,我们通常要做些多余的事,这就是传说中的线程安全。也就是说线程安全就是为了保证在多线程环境中,有且仅有一个线程能对当前属性进行set和get操作,其他线程必须等待其完成操作之后再进行操作。
回到咱们主题上,Objective-C中原子性就是为了保证线程安全而存在。如果当前的某一属性的属性为原子性,那么任何一个线程对其记性set和get方法时都会对当前的属性进行加锁和解锁操作(Objective-C中保证线程安全的一种方法)。从而保证其在多线程编程环境的线程安全。通常情况下,我们不会涉及过多的线程安全,并且加锁和解锁操作也会造成相当多的资源开销,所以我们一般都将属性设置为非原子性。但是苹果公司为了安全考虑出发,默认情况下,这个属性是非原子性
readwrite(读写) vs readonly (只读) 【访问控制】
这两个属性相对还是比较好理解的。这属性默认情况下是读写的,这就是为什么我们可以对实例变量进行取值和赋值的操作,其实质就是有set和get方法。通过这个说明相信聪明的你已经猜到只读的含义和实质了。只读的含义大家用心领悟一下,在这里我我说一下。其实就是就是只写了get方法,没有提供get方法。到这里不知道大家有没有想过一个问题。为啥没有只写方法,想了半天,突然发现,只写有不能读有啥用啊
另外如果你不喜欢编译器默认的set和get方法,你可以自定义set和get方法,声明如下
@property (getter=isRunning, readonly) BOOL running;
补充一句,在Swift中都是
isRunning
这种形式
为了操作的方便,苹果属性将读写属性设置为实例变量的默认属性
strong (强引用) vs weak(弱引用) vs assign(赋值) vs copy(复制)
每一遍编程语言都无法绕开的深渊--内存管理。特别是在移动设备上这种内存资源相对短缺的设备上。当然这个也是有相对来说的,比如现在Android手机最大的运行内存已经可达到4G,呵呵哒...... 但是就目前iOS设备来说,内存资源还是比较稀缺的(貌似iPhone 7要扩容了)。
在大多数的面向对象的语言中都是通过垃圾回收,但是苹果公司开发一套我认为相当流b的机制--对象拥有关系(object ownership)。专有的名词没找到,我就强行秀一波英语。大概就是说,当我们对一个对象就行操作时,我们必须确定他的存在,就是说我们必须拥有它。如果当前对象没有拥有者,那么操作系统(不是编译器)会在一个合适的时间(目前我也不确定,有种说法是两个runloop切换期间)将其销毁并释放掉其所占内存。这个机制的实现依赖于ARC机制。至于ARC机制在这里我不多讲,有一点跟大家说明,当对象拥有者增加时,当前对象的引用计数会+1,如果引用计数为0时,就是没有对象拥有,那么这个对象就可能被释放。
strong属性就是表示我拥有当前对象,并且当前对象的引用计数+1.strong有一个好处就是说,我能够确认当前对象的存在,因为只要我不消失,当前对象就不会被销毁,即不会消失。这就保证了我能够在需要的时候随时访问当前这个对象。
引用计数模型.png通过上面的介绍大家应该能推出来如果要释放一个对象,必须是他的引用计数(拥有者的数量)为0.好大家看下面这张图
循环引用计数模型.jpg简单说明一下,对象A强应用对象B,同时对象B也强引用对象A,这时候大家想象一下,当我程序执行结束后,操作系统会怎样释放这两个对象那,因为程序执行过程中,两个对象的引用计数都不可能为0,因而都不会被释放,但是当程序执行结束,会怎样?没错,这个就是经典的循环引用计数问题,而他的直接后果就是鼎鼎大名的“内存泄漏”。当程序执行完成之后,操作系统不会释放掉任何一方,从而导致两者一直留在内存中,导致我们的内存越来越下。
很明显这个不是我们想要的,但是以上的情况和多时候我们又无法避免。为了解决这个问题,另外一个属性被发明出来,他就说weak,同样咱们看一下模态图
弱引用解决循环引用模型.jpg简单说明,对象A此时仍然强引用对象B,而对象B弱引用对象A。细心同学发现此时对象B拥有对象A为虚线,并且对象A的引用计数并没有增加。或许你会想是不是画错了。我可以告诉NO,这这你weak的精髓。简单明了,当B释放的时候,我的两个对象都会被释放掉,并且当前对象A的指针会被置为nil。弱引用的实质就是和strong一样拥有当前对象,能够对象当前对象进行操作,但是不使其引用计数增加。这样就完美的解决了循环引用问题。给他家提个醒,不要乱用weak,可能会导致奇怪的bug。代理协议的实例对象通常设置为weak属性
至于copy这个属性可能是有人觉得比较茫然。如果一个属性被设置为copy属性时,对象记性读写操作,他获得的是当前的对象的一份copy,而不是简单对其进行应用。并且源对象的引用计数不会增加。通常经常下copy属性主要用于源对象可能发生改变,而不像当前对象受其影响。不如说我们将一个NSMutableString类型的String赋值给NSString类型的对象,这个时候我们为了防止修改NSMutableString对象对当前对象影响。我们会考虑将其设置为copy。通常情况string对象和block对象会被设置为copy属性。
总结一下,以上三个属性都是针对于对象来说的,但是大家都知道Objective-C不是一门新的编程语言,它只是在C语言的基础上加上了一层面向对象的特性。那么问题来了,C语言中有好多基础类型,声明这些属性时我们怎样设置的他的属性?别急,不还有一个吗?assign主要用来修饰Objective-C中基础属性。Objective-C支持64位以后全面更新了基础属性的定义。其实就是做了一些兼容64的修改,例如int -> NSInteger等。
当然还有一些其他的属性,比如unsafe_unretained,这个跟weak有很多相似,当时不同的是他不会将当前的对象指针置为nil。如果你需要使用他,请确定当前环境不支持weak,因为从他名字就知道这货不是很好。哈哈哈。
当然以上的所有都是针对于ARC机制下来说,对于老的开发程序员(就是MRC下开发的前辈们),还有retain等属性。在这里我就不再展开来说。我表示我赶上了MRC的尾巴,但是当时没有研究这个东东,感觉太坑了。就直接跳进了ARC的怀抱。现在想想,还是真是幸福啊。哈哈
网友评论