@Property那些事

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= 、setter=
    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 合成实例变量的规则,有以下几点:

  1. 如果指定了成员变量的名称,会生成一个指定的名称的成员变量,
  2. 如果这个成员已经存在了就不再生成了.
  3. 如果是 @synthesize xjy; 还会生成一个名称为xjy的成员变量,也就是说:如果没有指定成员变量的名称会自动生成一个属性同名的成员变量,
  4. 如果是 @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函数中使用这个实例对象的时候

ClassA属性.jpeg

发现只有_num3是可以访问的,其他两个都不能访问,_num2是手动设置为private属性,很显然是不能访问,_num1什么也没有设置,然而为什么也不能访问

我们来尝试给_num1赋值,看看是什么个情况

ClassANum1访问报错.jpeg

系统给出了很详细的解释,说 _num1protected,也就是说这里声明的属性默认是 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;

ClassANum1Property声明.jpeg

看到了系统默认生成的 setter 和 getter 方法,然后不管点击哪一个进入都会指向这一句话,因为我们没有在.m文件中手动实现num1的 setter 和 getter 方法,所以就会指向这里,可以发现@synthesize的作用就是告诉你如果没有手动实现setter和getter方法,编译器会自动帮你生成

ClassANum1Synthsize.jpeg

@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有一个好处就是说,我能够确认当前对象的存在,因为只要我不消失,当前对象就不会被销毁,即不会消失。这就保证了我能够在需要的时候随时访问当前这个对象。

@Property那些事_第1张图片
引用计数模型.png

通过上面的介绍大家应该能推出来如果要释放一个对象,必须是他的引用计数(拥有者的数量)为0.好大家看下面这张图

@Property那些事_第2张图片
循环引用计数模型.jpg

简单说明一下,对象A强应用对象B,同时对象B也强引用对象A,这时候大家想象一下,当我程序执行结束后,操作系统会怎样释放这两个对象那,因为程序执行过程中,两个对象的引用计数都不可能为0,因而都不会被释放,但是当程序执行结束,会怎样?没错,这个就是经典的循环引用计数问题,而他的直接后果就是鼎鼎大名的“内存泄漏”。当程序执行完成之后,操作系统不会释放掉任何一方,从而导致两者一直留在内存中,导致我们的内存越来越下。

很明显这个不是我们想要的,但是以上的情况和多时候我们又无法避免。为了解决这个问题,另外一个属性被发明出来,他就说weak,同样咱们看一下模态图

@Property那些事_第3张图片
弱引用解决循环引用模型.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的怀抱。现在想想,还是真是幸福啊。哈哈

你可能感兴趣的:(@Property那些事)