属性修饰符,顾名思义就是修饰属性的符号,针对不同的属性和使用场景有不同的属性修饰符
作用:
1、weak属性修饰符用来修饰对象,是弱引用,被weak修饰的对象,引用计数不会加1,当被引用的对象释放掉之后,weak会自动置为nil,从而避免了野指针访问无用内存(对象被释放)的时候而导致的crash。
2、weak修饰的对象可以避免循环引用
原理:
iOS runtime机制会针对每个对象生成一个weak表,这是一个hash表,key是被引用对象的指针,value是指向对象指针的weak指针地址数组(一个对象可以被多个弱引用指针指向)
weak修饰的对象的初始化到销毁过程:
- 首先runtime调用objc_initweak函数,将weak指针指向对象的地址
- objc_initweak的实现代码如下,实际上就是判断对象是否有效,然后调用objc_storeWeak()函数
idobjc_initWeak(id*location,id newObj) {
if(!newObj) {// 查看对象实例是否有效,无效对象直接导致指针释放,返回nil
*location = nil;
return nil;
}
// 使用 template 进行常量参数传递是为了优化性能
return storeWeakfalse/*old*/,true/*new*/,true/*crash*/>
(location, (objc_object*)newObj);
}
- objc_storeWeak()函数的主要作用是更新指针指向,创建对应的weak表(后面有storeweak函数的实现)
- weak指针是怎样释放的?先说下对象的释放流程
- 首先对象会调用objc_relaease
- 由于对象的引用计数为0,执行dealloc函数
- dealloc函数体里调用了_objc_rootDealloc
- _objc_rootDealloc调用了_objc_dispose函数
- 然后调用objc_distructinstance:销毁对象
- dealloc函数体里调用了_objc_rootDealloc
- 最后调用objc_clear_Deallocting函数:处理weak指针
- 通过对象的地址,在weak表中获取以对象地址为key的value数组(存放weak指针的数组)
- 然后遍历该数组将指针置为nil
- 从weak表中删除该记录
- 在引用计数表中删除以该对象地址为key的记录
补充
weak与assign的区别
1、weak只能用于修饰对象,assign可以用来修饰即本数据类型,也可以修饰对象,
2、assign修饰对象时对象置为nil时,assign修饰的属性依然指向内存地址,会导致野指针;而weak不会导致野指针
weak与strong的区别
objc_storeweak()函数的源码
// HaveOld: true - 变量有值
// false - 需要被及时清理,当前值可能为 nil
// HaveNew: true - 需要被分配的新值,当前值可能为 nil
// false - 不需要分配新值
// CrashIfDeallocating: true - 说明 newObj 已经释放或者 newObj 不支持弱引用,该过程需要暂停
// false - 用 nil 替代存储
template bool HaveOld, bool HaveNew, bool CrashIfDeallocating>
static id storeWeak(id *location, objc_object *newObj) {
// 该过程用来更新弱引用指针的指向
// 初始化 previouslyInitializedClass 指针
Class previouslyInitializedClass = nil;
id oldObj;
// 声明两个 SideTable
// ① 新旧散列创建
SideTable *oldTable;
SideTable *newTable;
// 获得新值和旧值的锁存位置(用地址作为唯一标示)
// 通过地址来建立索引标志,防止桶重复
// 下面指向的操作会改变旧值
retry:
if (HaveOld) {
// 更改指针,获得以 oldObj 为索引所存储的值地址
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (HaveNew) {
// 更改新值指针,获得以 newObj 为索引所存储的值地址
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
// 加锁操作,防止多线程中竞争冲突
SideTable::lockTwoHaveOld, HaveNew>(oldTable, newTable);
// 避免线程冲突重处理
// location 应该与 oldObj 保持一致,如果不同,说明当前的 location 已经处理过 oldObj 可是又被其他线程所修改
if (HaveOld && *location != oldObj) {
SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);
goto retry;
}
// 防止弱引用间死锁
// 并且通过 +initialize 初始化构造器保证所有弱引用的 isa 非空指向
if (HaveNew && newObj) {
// 获得新对象的 isa 指针
Class cls = newObj->getIsa();
// 判断 isa 非空且已经初始化
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized()) {
// 解锁
SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);
// 对其 isa 指针进行初始化
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));
// 如果该类已经完成执行 +initialize 方法是最理想情况
// 如果该类 +initialize 在线程中
// 例如 +initialize 正在调用 storeWeak 方法
// 需要手动对其增加保护策略,并设置 previouslyInitializedClass 指针进行标记
previouslyInitializedClass = cls;
// 重新尝试
goto retry;
}
}
// ② 清除旧值
if (HaveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// ③ 分配新值
if (HaveNew) {
newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table,
(id)newObj, location,
CrashIfDeallocating);
// 如果弱引用被释放 weak_register_no_lock 方法返回 nil
// 在引用计数表中设置若引用标记位
if (newObj && !newObj->isTaggedPointer()) {
// 弱引用位初始化操作
// 引用计数那张散列表的weak引用对象的引用计数中标识为weak引用
newObj->setWeaklyReferenced_nolock();
}
// 之前不要设置 location 对象,这里需要更改指针指向
*location = (id)newObj;
}
else {
// 没有新值,则无需更改
}
SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);
return (id)newObj;
}