属性关键字:
- atomic
- nonatomic
- strong
- weak
- copy
- assign
- retain
- readwrite
- readondy
- unsafe_unretained
属性关键字与所有权关键字之间是对应关系:
- assign 对应
__unsafe_unretained
修饰符 - copy 对应
__strong
修饰符 - retain 对应
__strong
修饰符 - strong 对应
__strong
修饰符 - unsafe_unretained 对应
__unsafe_unretained
修饰符 - weak 对应
__weak
修饰符
atomic:
多线程安全,默认关键字。
作用:多线程下将属性设置为atomic
可以保证读取数据的一致性,因为它保证数据只被一个线程占用,也就是说一个线程对属性进行写操作,会使用自旋锁
锁住该属性,不被其他线程进行读取操作。
缺点:使用自旋锁的开销会比较大(因为使用atomic在访问被锁的资源时,调用者线程不会休眠,而是不停循环在那里,直到被锁资源的锁被释放)。其实这也说明了atomic并不能真正的保证线程安全,因为自旋锁迟早就被释放。
在多线程环境下,原子操作是必要的,否则有可能引起错误的结果。加了atomic,setter函数会变成如下:
1 | {lock} // 自旋锁 |
为什么不能真正的保证线程安全:
例:
线程A 调用 getter
于此同时线程B 线程C 都调用了 setter
那线程A get 到的值就有三种情况:
1.可能是B/C线程set之前原始的值
2.可能是 B 线程 set 的值
3.可能是 C 线程 set 的值
因此atomic修饰的属性,系统在生成的getter/setter 会保证get/set的操作
完整性,不受其他线程的影响。getter 最总还是能得到一个完好的对象(保证了数据的完整性,就是不存在get不到值的情况)。多个线程同时调用setter方法的情况,虽然不会
出现某个线程执在未执行完setter全部语句之前,另一个线程就开始执行setter方法(确保操作完整性)。但是只是保证了在getter和setter方法上的线程安全,getter和setter以外的线程安全就无法保证了。
nonatomic:
在来理解nonatomic相对来说就比较轻松了,因为nonatomic理论上跟atomic是相对的。
即:非多线程、变量保护。因为没有自旋锁的存在,性能更优。但同时返回的对象或操作就有可能不是完整的。
- 分析strong和weak的实现之前,很有必要看看对象的成员变量第怎么进行赋值的。因为对象的属性的setter本质就是对对象的
成员变量
进行复制,一般情况下每一个对象的属性就对应存在一个对象的成员变量
weak:
弱引用,引用计数不会+1,并在引用对象被释放后指针会被至为nil。 用于一些对象相互引用的时候,避免出现强强引用,对象不能释放所造成的内存泄漏问题。
理论知识:weak是通过Runtime
维护的`weak表
,而strong是通过runtime维护的一个自动计数表结构。
weak表
:其实就是一个hash表(哈希表)。weak_table_t
是一个全局weak引用的表,使用不定类型对象的地址
作为key
,使用weak_entry_t
(保存了所有指向指定对象的weak指针数组)类型结构体对象作为value
。
weak
实现原理步骤
初始化
runtime
会调用objc_initWeak
函数,初始化一个新的weak指针指向对象的地址。
1 | // 前提条件: 必须是一个没有被注册为__weak对象的有效指针。而value则可以是null或指向一个有效的对象。 |
添加引用时
添加引用时objc_initWeak()
函数会调用storeWeak()
函数,用于更新指针指向,并创建对应的弱引用表
1 | static id |
释放时调用
释放时调用objc_clear_deallocating
函数.objc_clear_deallocating
函数首先根据对象地址获取所有weak指针地址的数组
,然后遍历这个数组把其中的数据设置为nil
,最后把这个entry从weak表中删除,最后清理对象的记录。
1 | void |
还调用了 clearDeallocating() 函数
1 | inline void |
clearDeallocating_slow()
1 | NEVER_INLINE void |
最后在weak_clear_no_lock()
函数中可以看出具体至nil的具体操作:
1 | /** |
SideTable
在NSObject.mm
文件中创建了一个结构体SideTable
,源码如下:
1 | struct SideTable { |
weak_table_t
1 | The global weak references table. Stores object ids as keys, |
1 | struct weak_table_t { |
作用:就是在对象执行dealloc的时候将所有指向该对象的weak指针的值设为nil,避免空指针。 使用不定类型对象的地址的hash化后的数值作为key,用weak_entry_t
类型的结构体对象作为value。
weak_entry_t
是存储在弱引用表中的一个内部结构体,负责维护和存储指向一个对象的所有弱引用hash表。存储了一个对象的引用计数的信息.
1 | #define WEAK_INLINE_COUNT 4 |
strong:
理解了weak再来理解strong就相对比较的简单了。因为weak多半是为了解决strong带来的一些问题而存在的。强引用,被strong修饰的属性一般不会自动释放。
assign:
既可以修饰对象类型的,也可以修饰基本数据类型。引用计数不 +1,当被修饰的对象被释放后指针不会被至为nil(跟weak相反)会产生野指针,因此向被释放的对象发送消息会导致崩溃。setter
方法直接赋值,不进行任何retain
操作,为了解决原类型与循环引用问题。
copy:
主要使用在两个方面:
1.block
: 在MRC时代block内部的代码块是在栈区(由系统编译器自动管理,不需要程序员手动管理)的,使用copy关键字可以把它放到堆区。 ARC使用copy和strong效果是一样的。
- 很有必要理解下为什么要把
block
把到堆区:
其实先大致理解下
栈
和堆
的意思。
栈
:有系统编译器自动管理,不需要程序员手动管理。一般用于存放非OC对象
堆
:释放由程序员手动管理,因此不及时回收很容易产生内存泄漏情况。一般用于存放OC对象
。然而
block
就是一个对象,理论上是可以进行retain/release。但是block在创建的时候它的内存默认是分配在栈
上的,所以它的作用域仅限在创建的时候的上下文函数或方法。所以当你在该作用域外调用该block程序就会崩溃。把block的内存推到堆区就可以避免这个问题。
2.用于某些父类指针可以指向子类的对象(多指存在不可变和可变类型的对象:NSString、NSArray、NSDictionary…),作用就是保护属性变量不会被修改。
1 | @property (nonatomic,strong) NSString * strongedStr; |
可以很明显的看出使用
strong
修饰的不可变
类型的地址跟可变
类型的地址是相同的,
使用copy
则是新地址。
1 | // 改变mutable_string 的值后看下内存地址变化 |
可以很明显的看出
copy
修饰的值
没有改变,但是内存地址却发生了改变。strong
修饰的则都随着改变。
- 由此可以引出copy的
深拷贝
和浅拷贝
的概念和区别。
深拷贝:真正的复制,复制的对象指向新的地址。
> 真正理解
深拷贝
和浅拷贝
还需要了解容器类
和非容器类
。>
>
容器类
:可以包容其他类的基类。如:NSarray,NSDictionary>
>
非容器类
:则不可以可以包容其他类的基类。如:NSString>
> 因为
容器类
和非容器类
所涉及的深拷贝
和浅拷贝
也是有所区别的。####
非容器类
:
不可变
:
1 | NSString *string = @"非容器不可变类型"; |
结论:
1.非容器 -> 不可变对象 -> copy -> 浅拷贝:地址相同 不可变对象 2.非容器 -> 不可变对象 -> mutableCopy -> 深拷贝:地址不同 可变对象可变
:
1 | NSMutableString *mustablestring = [[NSMutableString alloc] initWithString:@"非容器可变类型"]; |
结论:
1.非容器 -> 可变对象 -> copy -> 深拷贝:地址不同 可变对象 2.非容器 -> 可变对象 -> mutableCopy -> 深拷贝:地址不同 可变对象容器类
:
不可变
:
1 | NSArray *array = @[@"1",@"2",@"3"]; |
结论:
1.容器 -> 不可变对象 -> copy -> 浅拷贝:地址相同 不可变对象 2.容器 -> 不可变对象 -> mutableCopy -> 深拷贝:地址不同 可变对象可变
:
1 | mutablearray--值 = (1,2,3) |
结论:
1.容器 -> 可变对象 -> copy -> 深拷贝:地址不同 不可变对象 2.容器 -> 可变对象 -> mutableCopy -> 深拷贝:地址不同 可变对象总结:
非容器 | ||
---|---|---|
方法类型 | 不可变类型 | 可变类型 |
copy | 浅拷贝 | 深拷贝 |
mutableCopy | 深拷贝 | 深拷贝 |
容器 | ||
copy | 浅拷贝 | 深拷贝 |
mutableCopy | 深拷贝 | 深拷贝 |
copy对于可变对象为深拷贝,对于不可变对象为浅拷贝
mutableCopy都是深拷贝
retain:
持有特效性,setter方法会将传入的参数先保留,在赋值。释放旧的对象,将旧对象的值赋予输入对象,在提高输入对象的引用计数值 +1。(release旧值,retain新值)
- 声明
属性
时效果等同于strong
,而在声明block
时使用的strong
与copy
是等效的,但是retain
却不等效于strong
而等效于assign
。