039 *:property 后面可以有哪些修饰符?(线程安全、读写、内存管理)(atomic,nonatomic,readonly,readwrite,assign, copy, strong,weak,Retain)(weak和assign)(浅拷贝、单层深拷贝、深拷贝) 可变数组(环形缓冲区)字典(哈希表)
一:@property 后面可以有哪些修饰符?
1:线程安全的:
atomic,nonatomic
2:访问权限的
readonly,readwrite
3:内存管理(ARC)
assign, copy, strong,weak,
4: 内存管理(MRC)
assign,retain,copy
ARC 下,不显式指定任何属性关键字时,默认的关键字都有哪些?
基本数据: atomic,readwrite,assign
普通的 OC 对象: atomic,readwrite,strong
二、 readwrite,readonly,assign,retain,copy,nonatomic,atomic,strong,weak属性的作用分别是什么。
关键字 | 注释 |
---|---|
readwrite | 此标记说明属性会被当成读写的,这也是默认属性。 |
readonly | 此标记说明属性只可以读,也就是不能设置,可以获取。 |
assign | 不会使引用计数加1,也就是直接赋值。 |
retain | 会使引用计数加1。 |
copy | 建立一个索引计数为1的对象,在赋值时使用传入值的一份拷贝。 |
nonatomic | 非原子性访问,多线程并发访问会提高性能。 |
atomic | 原子性访问。 |
strong | 打开ARC时才会使用,相当于retain。 |
weak | 打开ARC时才会使用,相当于assign,可以把对应的指针变量置为nil。 |
三:原子性:非原子性
开发过程中,setter和getter方法处处都在使用,如果使用atomic修饰,setter和getter方法内部会做很多多线程安全的操作,会很占用系统资源,降低系统性能。
所以在平常开发中原子性(线程安全)一般设置为nonatomic,只有在需要安全的地方atomic
说atomic与nonatomic的本质区别其实也就是在setter方法和get方法的操作不同:加锁
// nonatomic的实现: - (void)setCurrentImage:(UIImage *)currentImage { if (_currentImage != currentImage) { [_currentImage release]; _currentImage = [currentImage retain]; // do something } } - (UIImage *)currentImage { return _currentImage; } // atomic的实现: - (void)setCurrentImage:(UIImage *)currentImage { @synchronized(self) { if (_currentImage != currentImage) { [_currentImage release]; _currentImage = [currentImage retain]; // do something } } } - (UIImage *)currentImage { @synchronized(self) { return _currentImage; } }
四:读写访问权限
读写性的控制(readonly,readwrite,setter,getter)
readonly:只读属性,告诉编译器之声明getter方法,而没有setter(只能读取值,不能被赋值)
readwrite:读写属性,告诉编译器,既声明setter又声明getter方法,readwrite是属性读写性控制的默认修饰词
五:内存权限
1:weak和assign的区别-正确使用weak、assign
很少有人知道
1: weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址数组。
2: 更多人的人只是知道weak是弱引用,所引用对象的计数器不会加一,并在引用对象被释放的时候自动被设置为nil。通常用于解决循环引用问题。
3: 但现在单知道这些已经不足以应对面试了,好多公司会问weak的原理。weak的原理是什么呢?下面就分析一下weak的工作原理(只是自己对这个问题好奇,学习过程中的笔记,希望对读者也有所帮助)。
weak 实现原理的概括
Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象的地址)数组。
weak 的实现原理可以概括一下三步:
1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
六:weak和assign区别
区别
a.修饰变量类型的区别
weak 只可以修饰对象。如果修饰基本数据类型,编译器会报错-“Property with ‘weak’ attribute must be of object type”。
assign 可修饰对象,和基本数据类型。当需要修饰对象类型时,MRC时代使用unsafe_unretained。当然,unsafe_unretained也可能产生野指针,所以它名字是"unsafe_”。
b.是否产生野指针的区别
weak 不会产生野指针问题。因为weak修饰的对象释放后(引用计数器值为0),指针会自动被置nil,之后再向该对象发消息也不会崩溃。 weak是安全的。
assign 如果修饰对象,会产生野指针问题;如果修饰基本数据类型则是安全的。修饰的对象释放后,指针不会自动被置空,此时向对象发消息会崩溃。
c、相似
都可以修饰对象类型,但是assign修饰对象会存在问题。
d、总结
assign 适用于基本数据类型如int,float,struct等值类型,不适用于引用类型。
因为值类型会被放入栈中,遵循先进后出原则,由系统负责管理栈内存。
而引用类型会被放入堆中,需要我们自己手动管理内存或通过ARC管理。
weak 适用于delegate和block等引用类型,不会导致野指针问题,也不会循环引用,非常安全。
七:copy
1: 怎么用 copy 关键字
NSString、NSArray、NSDictionary 等等经常使用 copy 关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary.
为确保对象中的属性值不会无意间变动,应该在设置新属性值时拷贝一份,保护其封装性.
block,也经常使用 copy,关键字block。
使用 copy 是从 MRC 遗留下来的“传统”,在 MRC 中,方法内部的 block 是在栈区的,使用 copy 可以把它放到堆区.
在 ARC 中写不写都行:对于 block 使用 copy 还是 strong 效果是一样的,但是建议写上 copy,因为这样显示告知调用者“编译器会自动对 block 进行了 copy 操作。
2、用@property 声明的 NSString(或 NSArray,NSDictionary)经常使用 copy 关键字,为什么?如果改用 strong 关键字,可能造成什么问题?
因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本.
如果我们使用是 strong,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性
c:深浅copy
七:block中copy
1:下面进入主题为什么要用copy去修饰block呢
个人理解:默认情况下,block会存档在栈中(栈是吃了吐),所以block会在函数调用结束被销毁,在调用会报空指针异常,如果用copy修饰的话,可以使其保存在堆区(堆是吃了拉) ,它的生命周期会随着对象的销毁而结束的。只要对象不销毁,我们就可以调用在堆中的block。
在了解block为什么要用copy之前,我们要先了解block的三种类型
一 NSGlobalBlock:全局的静态block 没有访问外部变量 你的block类型就是这种类型(也就是说你的block没有调用其他外部变量)
二 NSStackBlock:保存在栈中的block,没有用copy去修饰并且访问了外部变量,你的block类型就是这种类型,会在函数调用结束被销毁 (需要在MRC)
三 NSMallocBlock 保存在堆中的block 此类型blcok是用copy修饰出来的block 它会随着对象的销毁而销毁,只要对象不销毁,我们就可以调用的到在堆中的block。
2.1:首先,我们要明确一点,为什么要用copy修饰,这是因为在MRC时期,作为全局变量的block在初始化时是被存放在静态区的,这样在使用时如果block内有调用外部变量,那么block无法保留其内存,在初始化的作用域内使用并不会有什么影响,但一但出了block的初始化作用域,就会引起崩溃,使用copy可以将block的内存推入堆中,这样让其拥有保存调用的外部变量的内存的能力。
2.2:(将block存入堆区带来的一个问题,self会持有block的引用,那么在block里使用self会导致循环引用,这也是为什么在MRC和ARC时期要分别用__block和__weak来修饰self的原因)
既然使用copy的原因是为了让Block在初始化作用域外进行正常访问外部变量,那我们就来看使用strong能不能达到这种效果。
3:外部使用了weakSelf,里面使用strongSelf却不会造成循环,究其原因就是因为weakSelf是block截获的属性,而strongSelf是一个局部变量会在“函数”执行完释放。
是为了保证block执行完毕之前self不会被释放,执行完毕的时候再释放。这时候会发现为什么在block外边使用了__weak修饰self,里面使用__strong修饰weakSelf的时候不会发生循环引用?!
4.1:__weak
本身是可以避免循环引用的问题的,但是其会导致外部对象释放了之后,block 内部也访问不到这个对象的问题,我们可以通过在 block 内部声明一个 __strong
的变量来指向 weakObj,使外部对象既能在 block 内部保持住,又能避免循环引用的问题。
4.2:__block
本身无法避免循环引用的问题,但是我们可以通过在 block 内部手动把 blockObj 赋值为 nil 的方式来避免循环引用的问题。
另外一点就是 __block
修饰的变量在 block 内外都是唯一的,要注意这个特性可能带来的隐患。但是__block
有一点:这只是限制在ARC环境下。在非arc下,__block是可以避免引用循环的
4.3:另外,MRC中__block是不会引起retain;但在ARC中__block则会引起retain。所以ARC中应该使用__weak。
5:为什么使用weakSelf
通过 clang -rewrite-objc 源代码文件名 将代码转为c++代码(实质是c代码),可以看到block是一个结构体,它会将全局变量(self)保存为一个属性(是__strong的),而self强引用了block这会造成循环 引用。所以需要使用__weak修饰的weakSelf。
为什么在block里面需要使用strongSelf
6: MRC使用了retain修饰的block崩溃掉了,当将MRC切换回ARC,并且把修饰符换为strong时,并没有发生崩溃,说明该block被推入了堆,拥有了保存外部变量内存的能力。