property修饰符copy、weak等具体讲解

property属性修饰符经常的用法

当我们定义一个字符串属性时,通常我们会这样写:

@property (nonatomic, copy) NSString *name;

当我们定义一个NSMutableArray类型的属性时,通常我们会这样写:

@property (nonatomic, strong) NSMutableArray *books;

而当我们定一个基本数据类型时,会这样写:

@property (nonatomic, assign) int age;

定义一个属性时,nonatomic、copy、strong、assign等被称作是关键字,或者是修饰符。

修饰符种类

原子性:原子性有nonatomic、atomic两个值,如果不写nonatomic,那么默认是atomic的。如果属性是atomic的,那么在访问其getter和setter方法之前,会有一些判断,大概是判断是否可以访问等,这里系统使用的是自旋锁。由于使用atomic并不能绝对保证线程安全,且会耗费一些性能,因此通常情况下都使用nonatomic。

读写权限:读写权限有两个取值,readwrite和readonly。声明属性时,如果不指定读写权限,那么默认是readwrite的。如果某个属性不想让其他人来写,那么可以设置成readonly。

内存管理:内存管理的取值有assign、strong、weak、copy、unsafe_unretained、retain

存取方法名称:getter=,setter=(不常用)

set、get方法名。如果不想使用自动合成所生成的setter、getter方法,声明属性时甚至可以指定方法名。比如指定getter方法名:

@property (nonatomic, assign, getter=isPass) BOOL pass;

属性pass的getter方法就是

- (BOOL)isPass;

不常用:nonnull,null_resettable,nullable(一些系统方法中会出现)

默认修饰符

声明属性时,如果不显示指定修饰符,那么默认的修饰符是哪些呢?或者说未指定的修饰符,默认取值是什么呢?如果是基本数据类型,默认取值是:

atomic,readwrite,assign

如果是Objective-C对象,默认取值是:

atomic,readwrite,strong

atomic是否是线程安全的

上面提到了,声明属性时,通常使用nonatomic修饰符,原因就是因为atomic并不能保证绝对的线程安全。举例来说,假设有一个线程A在不断的读取属性name的值,同时有一个线程B修改了属性name的值,那么即使属性name是atomic,线程A读到的仍旧是修改后的值,可见不是线程安全的。如果想要实现线程安全,需要手动的实现锁。下面是一段示例代码:

声明name属性,使用atomic修饰符

@property (atomic, copy) NSString *name;

对属性name赋值。同时,一个线程在不断的读取name的值,另一个线程在不断的设置name的值:

stu.name = @"aaa";
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
 for(int i = 0 ; i < 1000; ++i){
 NSLog(@"stu.name = %@",stu.name);
 }
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
 stu.name = @"bbb";
});

看一下输出:

2018-12-06 15:42:26.837215+0800 TestClock[15405:175815] stu.name = aaa
2018-12-06 15:42:26.837837+0800 TestClock[15405:175815] stu.name = bbb

证实了即使使用了atomic,也不能保证线程安全。

weak和assign区别

经常会有面试题问weak和assign的区别,这里介绍一下。

weak和strong是对应的,一个是强引用,一个是弱引用。weak和assign的区别主要是体现在两者修饰OC对象时的差异。上面也介绍过,assign通常用来修饰基本数据类型,如int、float、BOOL等,weak用来修饰OC对象,如UIButton、UIView等。

如果基本数据类型用weak来修饰

假设声明一个int类型的属性,但是用weak来修饰,会发生什么呢?

@property (nonatomic, weak) int age;

Xcode会直接提示错误,错误信息如下:

Property with 'weak' attribute must be of object type

也就是说,weak只能用来修饰对象,不能用来修饰基本数据类型,否则会发生编译错误。

如果对象使用assign来修饰

假设声明一个UIButton类型的属性,但是用assign来修饰,会发生什么呢?

@property (nonatomic, assign) UIButton *assignBtn;

编译,没有问题,运行也没有问题。我们再声明一个UIButton,使用weak来修饰,对比一下:

@interface ViewController ()
@property (nonatomic, assign) UIButton *assignBtn;
@property (nonatomic, weak) UIButton *weakButton;
@end

正常初始化两个button:

UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(100,100,100,100)];
[btn setTitle:@"Test" forState:UIControlStateNormal];
btn.backgroundColor = [UIColor lightGrayColor];
self.assignBtn = btn;
self.weakButton = btn;

此时打印两个button,没有区别。释放button:

btn = nil;

释放之后打印self.weakBtn和self.assignBtn

NSLog(@"self.weakBtn = %@",self.weakButton);
NSLog(@"self.assignBtn = %@",self.assignBtn);

运行,执行到self.assignBtn的时候崩溃了,崩溃信息是

 EXC_BAD_ACCESS (code=EXC_I386_GPFLT)

weak和assign修饰对象时的差别体现出来了。

weak修饰的对象,当对象释放之后,即引用计数为0时,对象会置为nil

2018-12-06 16:17:05.774298+0800 TestClock[15863:192570] self.weakBtn = (null)

复制代码而向nil发送消息是没有问题的,不会崩溃。

assign修饰的对象,当对象释放之后,即引用计数为0时,对象会变为野指针,不知道指向哪,再向该对象发消息,非常容易崩溃。

因此,当属性类型是对象时,不要使用assign,会带来一些风险。

堆和栈

上面说到,属性用assign修饰,当被释放后,容易变为野指针,容易带来崩溃问题,那么,为何基本数据类型可以用assign来修饰呢?这就涉及到堆和栈的问题。

相对来说,堆的空间大,通常是不连续的结构,使用链表结构。使用堆中的空间,需要开发者自己去释放。OC中的对象,如 UIButton 、UILabel ,[[UIButton alloc] init] 出来的,都是分配在堆空间上。

栈的空间小,约1M左右,是一段连续的结构。栈中的空间,开发者不需要管,系统会帮忙处理。iOS开发 中 int、float等变量分配内存时是在栈上。如果栈空间使用完,会发生栈溢出的错误。

由于堆、栈结构的差异,栈和堆分配空间时的寻址方式也是不一样的。因为栈是连续的控件,所以栈在分配空间时,会直接在未使用的空间中分配一段出来,供程序使用;如果剩下的空间不够大,直接栈溢出;堆是不连续的,堆寻找合适空间时,是顺着链表结点来寻找,找到第一块足够大的空间时,分配空间,返回。根据两者的数据结构,可以推断,堆空间上是存在碎片的。

回到问题,为何assign修饰基本数据类型没有野指针的问题?因为这些基本数据类型是分配在栈上,栈上空间的分配和回收都是系统来处理的,因此开发者无需关注,也就不会产生野指针的问题。

栈是线程安全的嘛

扩展一下,栈是线程安全的嘛?回答问题之前,先看一下进程和线程的关系。

进程和线程的关系

线程是进程的一个实体,是CPU调度和分派的基本单位。一个进程可以拥有多个线程。线程本身是不配拥有系统资源的,只拥有很少的,运行中必不可少的资源(如程序计数器、寄存器、栈)。但是线程可以与同属于一个进程的其他线程,共享进程所拥有的资源。一个进程中所有的线程共享该进程的地址空间,但是每个线程有自己独立的栈,iOS系统中,每个线程栈的大小是1M。而堆则不同。堆是进程所独有的,通常一个进程有一个堆,这个堆为本进程中的所有线程所共享。

栈的线程安全

其实通过上面的介绍,该问题答案已经很明显了:栈是线程安全的。

堆是多个线程所共有的空间,操作系统在对进程进行初始化的时候,会对堆进行分配;

栈是每个线程所独有的,保存线程的运行状态和局部变量。栈在线程开始的时化,每个线程的栈是互相独立的,因此栈是线程安全的。

copy、mutableCopy、strong、weak

copy和strong

首先看一下copy和strong,copy和strong的区别也是面试中出现频率最高的。之前举得例子中其实已经出现了copy和strong:

@property (nonatomic, copy) NSString *sex;
@property (nonatomic, strong) NSMutableArray *books;

复制代码通常情况下,不可变对象属性修饰符使用copy,可变对象属性修饰符使用strong。

strong和weak

声明为weak的指针,指针指向的地址一旦被释放,这些指针都将被赋值为 nil

区别是当一个对象不再有strong类型的指针指向它的时候 它会被释放 ,即使还有weak型指针指向它。

深拷贝、浅拷贝

所谓浅拷贝,在Objective-C中可以理解为引用计数加1,并没有申请新的内存区域,只是另外一个指针指向了该区域。深拷贝正好相反,深拷贝会申请新的内存区域,原内存区域的引用计数不变。

可变对象的copy和mutableCopy都是深拷贝

可变对象NSMutableString和NSMutableArray为例,测试代码:

- (void)testMutableCopy
{
 NSMutableString *str1 = [NSMutableString stringWithString:@"abc"];
 NSString *str2 = [str1 copy];
 NSMutableString *str3 = [str1 mutableCopy];
 NSLog(@"str1 = %p str2 = %p str3 = %p",str1,str2,str3);
 NSMutableArray *array1 = [NSMutableArray arrayWithObjects:@"a",@"b", nil];
 NSArray *array2 = [array1 copy];
 NSMutableArray *array3 = [array1 mutableCopy];
 NSLog(@"array1 = %p array2 = %p array3 = %p",array1,array2,array3);
}

输出结果:

2018-12-07 13:01:27.525064+0800 TestClock[9357:143436] str1 = 0x60000086d8f0 str2 = 0xc8c1a5736a50d5fe str3 = 0x60000086d9b0
2018-12-07 13:01:27.525198+0800 TestClock[9357:143436] array1 = 0x600000868000 array2 = 0x60000067e5a0 array3 = 0x600000868030

可以看到,只要是可变对象,无论是集合对象,还是非集合对象,copy和mutableCopy都是深拷贝。

不可变对象的copy是浅拷贝,mutableCopy是深拷贝

以NSString和NSArray为例,测试代码如下:

 (void)testCopy
{
 NSString *str1 = @"123";
 NSString *str2 = [str1 copy];
 NSMutableString *str3 = [str1 mutableCopy];
 NSLog(@"str1 = %p str2 = %p str3 = %p",str1,str2,str3);
 NSArray *array1 = @[@"1",@"2"];
 NSArray *array2 = [array1 copy];
 NSMutableArray *array3 = [array1 mutableCopy];
 NSLog(@"array1 = %p array2 = %p array3 = %p",array1,array2,array3);
}

输出结果:

208-12-07 13:06:29.439108+0800 TestClock[9442:147133] str1 = 0x1045612b0 str2 = 0x1045612b0 str3 = 0x6000017e4450
2018-12-07 13:06:29.439236+0800 TestClock[9442:147133] array1 = 0x6000019f5c80 array2 = 0x6000019f5c80 array3 = 0x6000017e1170

可以看到,只要是不可变对象,无论是集合对象,还是非集合对象,copy都是浅拷贝,mutableCopy都是深拷贝。

自定义对象如何支持copy方法

项目开发中经常会有自定义对象的需求,那么自定义对象是否可以copy呢?如何支持copy?

自定义对象可以支持copy方法,我们所需要做的是:自定义对象遵守NSCopying协议,且实现copyWithZone方法。NSCopying协议是系统提供的,直接使用即可。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值