前言
在上一篇文章是的时候,objc_setProperty方法的实现并没有体现strong和weak这两个修饰词,所以这两个修饰词是有另外的实现,而且是在上已层实现的;copy修饰词的话,只有copyWithZone和mutableCopyWithZone方法的调用,没有具体实现。
在看这么多的blog的时候,上过当,吃过亏。所以还是从源代码入手去更好理解这三个修饰词吧。
类的成员变量
分析strong和weak的实现之前,先看看对象的成员变量是怎么是进行赋值。
为什么呢?因为对象的属性的setter本质就是对对象的成员变量进行复制,一般情况下,每一个对象的属性就对应存在一个对象的成员变量。所以本质上即使在了解成员变量的strong和weak实现。
成员变量的getter
先看一下成员变量的结构体定义
//Ivar本质就是一个objc_ivar结构体,在objc_ivar结构体记录对象成员变量的信息
//这是在runtime.h头文件中的定义
typedef struct objc_ivar *Ivar;
struct objc_ivar {
//变量名
char * _Nullable ivar_name OBJC2_UNAVAILABLE;
//变量的数据类型
char * _Nullable ivar_type OBJC2_UNAVAILABLE;
//变量在对象指针的偏移量,也就是变量存放在内存的实际位置
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
//这是在objc-class-old.mm文件中的定义,和上面的objc_ivar的机构体是一样的,只是名称不一样。
struct old_ivar {
char *ivar_name;
char *ivar_type;
int ivar_offset;
#ifdef __LP64__
int space;
#endif
};
//在objc-class-old.mm文件中的定义了这么几个宏
//应该是做了一个新旧结构体的映射
#define oldprotocol(proto) ((struct old_protocol *)proto)
#define oldmethod(meth) ((struct old_method *)meth)
#define oldcategory(cat) ((struct old_category *)cat)
#define oldivar(ivar) ((struct old_ivar *)ivar)
#define oldproperty(prop) ((struct old_property *)prop)
//在objc-class-old.mm文件中,这个函数是获取成员变量在对象的相对偏移量
ptrdiff_t ivar_getOffset(Ivar ivar) {
return oldivar(ivar)->ivar_offset;
}
//这是在objc-runtime-new中定义的函数
ptrdiff_t ivar_getOffset(Ivar ivar) {
if (!ivar) return 0;
return *ivar->offset;
}
在获取成员变量,先了解上面成员变量的结构,可以看到在新旧系统中会有细微的差异,但是结构体的结构是一样的。
下面就是成员变量获取的实际实现了,下面就一句一句代码的分析里面的逻辑
/**
查找类成员变量的函数
cls:要查找的类
ivar:要查找的变量,通过变量的结构体对这个变量进行描述,包括变量名、变量类型和变量的指针便宜量
ivarOffset:变量相对于对象所在位置的指针偏移量
memoryManagement:内存管理信息
*/
static void
_class_lookUpIvar(Class cls, Ivar ivar, ptrdiff_t& ivarOffset,
objc_ivar_memory_management_t& memoryManagement)
{
ivarOffset = ivar_getOffset(ivar);
// Look for ARC variables and ARC-style weak.
// Preflight the hasAutomaticIvars check
// because _class_getClassForIvar() may need to take locks.
//查找ARC成员变量和ARC内存管理的weak
//提前检测是不是AutomaticIvars,其实就是是否开启ARC
//
bool hasAutomaticIvars = NO;
for (Class c = cls; c; c = c->superclass) {
if (c->hasAutomaticIvars()) {
hasAutomaticIvars = YES;
break;
}
}
if (hasAutomaticIvars) {
Class ivarCls = _class_getClassForIvar(cls, ivar);
if (ivarCls->hasAutomaticIvars()) {
// ARC layout bitmaps encode the class's own ivars only.
// Use alignedInstanceStart() because unaligned bytes at the start
// of this class's ivars are not represented in the layout bitmap.
//ARC 只针对类自身的变量输出位图编码
//使用alignedInstanceStart()是因为类的变量非位对齐其起始位置,并不代表在输出位图上
ptrdiff_t localOffset =
ivarOffset - ivarCls->alignedInstanceStart();
//查找成员变量的指针偏移量是否在类的变量输出位置
if (isScanned(localOffset, class_getIvarLayout(ivarCls))) {
//如果是,那么就是强引用的管理方式,因为属于类自身的变量,并返回
memoryManagement = objc_ivar_memoryStrong;
return;
}
//查找成员变量的指针偏移量是否在弱变量区域
if (isScanned(localOffset, class_getWeakIvarLayout(ivarCls))) {
//如果是,那么就是弱引用的管理方式,并返回
memoryManagement = objc_ivar_memoryWeak;
return;
}
// Unretained is only for true ARC classes.
//这个类是ARC管理的其他情况就是Unretained的变量
if (ivarCls->isARC()) {
memoryManagement = objc_ivar_memoryUnretained;
return;
}
}
}
//非ARC内存管理的,那么就赋值为未知内存管理方式
memoryManagement = objc_ivar_memoryUnknown;
}
/**
获取对象的成员变量
obj:对象
ivar:成员变量的描述,包括成员变量的名称、类型和偏移量
*/
id object_getIvar(id obj, Ivar ivar)
{
if (!obj || !ivar || obj->isTaggedPointer()) return nil;
//成员变量的指针偏移量,用来存放查找偏移量结果
ptrdiff_t offset;
//成员变量的内存管理方式,用来存放查找变量对应的内存管理方式
objc_ivar_memory_management_t memoryManagement;
//通过对象的类,去查找成员变量,并且将偏移指针存放在offset,内存方式存放在memoryManagement
_class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement);
//通过取到成员变量的偏移量,以对象为起始位置计算出其实际位置,获取对象指针
id *location = (id *)((char *)obj + offset);
//如果是弱应用
if (memoryManagement == objc_ivar_memoryWeak) {
//则在弱应用表中找出对应的对象
return objc_loadWeak(location);
} else {
//否则,直接返回指向的对象
return *location;
}
}
通过以上这段代码,可以得到下面几点总结:
- ARC内存管理是在这一层实现的
成员变量的setter
/**
对对象成员变量进行赋值,这是成员变量赋值在runtime层的实现
obj:需要赋值的对象
name:成员遍历那个的名称
value:性质
assumeStrong:需要设置的内存管理方式
*/
static ALWAYS_INLINE
void _object_setIvar(id obj, Ivar ivar, id value, bool assumeStrong)
{
//空值判断
if (!obj || !ivar || obj->isTaggedPointer()) return;
ptrdiff_t offset;
objc_ivar_memory_management_t memoryManagement;
//获取对象成员变量的指针偏移量和内存管理方式
_class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement);
//如果内存管理方式为未知
if (memoryManagement == objc_ivar_memoryUnknown) {
//而且指定了strong强引用的,修正memoryManagement
if (assumeStrong) memoryManagement = objc_ivar_memoryStrong;
//没指定为strong强引用的,修正为Unretained
else memoryManagement = objc_ivar_memoryUnretained;
}
//根据指针偏移量,获取成员变量指向的对象指针
id *location = (id *)((char *)obj + offset);
switch (memoryManagement) {
//弱引用,则将新值存放在弱引用weak表
case objc_ivar_memoryWeak: objc_storeWeak(location, value); break;
//强引用,则将新值存放在强引用的strong表
case objc_ivar_memoryStrong: objc_storeStrong(location, value); break;
//Unretained的,则直接赋值
case objc_ivar_memoryUnretained: *location = value; break;
//这种情况不可能发生
case objc_ivar_memoryUnknown: _objc_fatal("impossible");
}
}
//非strong默认的成员变量进行赋值
void object_setIvar(id obj, Ivar ivar, id value)
{
return _object_setIvar(obj, ivar, value, false /*not strong default*/);
}
//对默认是strong的成员变量进行赋值
void object_setIvarWithStrongDefault(id obj, Ivar ivar, id value)
{
return _object_setIvar(obj, ivar, value, true /*strong default*/);
}
/**
对对象成员变量进行赋值,这是成员变量赋值在runtime层的实现
obj:需要赋值的对象
name:成员遍历那个的名称
value:性质
assumeStrong:需要设置的内存管理方式
*/
static ALWAYS_INLINE
Ivar _object_setInstanceVariable(id obj, const char *name, void *value,
bool assumeStrong)
{
Ivar ivar = nil;
if (obj && name && !obj->isTaggedPointer()) {
//通过给定的名字,查找成员变量
if ((ivar = _class_getVariable(obj->ISA(), name))) {
_object_setIvar(obj, ivar, (id)value, assumeStrong);
}
}
return ivar;
}
//给对象成员变量进行复制
Ivar object_setInstanceVariable(id obj, const char *name, void *value)
{
return _object_setInstanceVariable(obj, name, value, false);
}
//给对象用strong默认修饰的成员变量进行赋值
Ivar object_setInstanceVariableWithStrongDefault(id obj, const char *name,
void *value)
{
return _object_setInstanceVariable(obj, name, value, true);
}
由上面这段代码可以看出来,可以总结出来下面几点:
- 所有对成员变量的赋值,都是通过调用_object_setIvar函数实现,_object_setIvar函数是具体实现,通过参数区分功能
- 对象和对象的成员变量是放在一片连续的内存块上面的,在编译期在类中已经确定成员变量相对于对象的的位置
- weak和strong修饰的成员变量,分别存放在不同的表中
- Unretained修饰成员变量只做赋值操作
strong修饰词
在上面成员变量的setter上可以看到,对于用strong修饰的成员变量的赋值,是调用objc_storeStrong函数来实现的,那么现在看看objc_storeStrong的具体实现是怎样的
void
objc_storeStrong(id *location, id obj)
{
//将*location指向的对象赋值给prev
id prev = *location;
//如果新值和原来的值一致,则返回,无需继续操作
if (obj == prev) {
return;
}
//对新值引用计数加一
objc_retain(obj);
//将对象指针指向新值
*location = obj;
//释放旧值
objc_release(prev);
}
从源代码上看,对于strong修饰的成员变量操作比较简单,整个过程就是对新值引用计数加一,和对旧值进行释放
weak修饰词
strong修饰词的成员变量赋值比较简单,那么现在看看objc_storeWeak的源代码,看看objc_storeWeak是怎么工作的
/**
localtion : 对象指针
nebObj:新值
*/
static id
storeWeak(id *location, objc_object *newObj)
{
assert(haveOld || haveNew);
//根据haveNew参数判断是否有新值,如果有新值,而且新值指针指向的对象为空,则提示
if (!haveNew) assert(newObj == nil);
Class previouslyInitializedClass = nil;
//旧值变量,用于存放旧值
id oldObj;
//旧值的weak引用表,用于记录该值的被引用情况
SideTable *oldTable;
//新值的weak引用表,用于记录新值的被引用情况
SideTable *newTable;
/**
需要同时获取旧值和新值的锁
为了解决锁定地址阻止锁的顺序问题
如果旧值正在修改,那么跳回retry重新做一遍流程
*/
retry:
if (haveOld) {
//如果haveOld的参数为真,那么根据location的对象指针获取旧值引用
oldObj = *location;
//则根据oldObj对象在全局的一个hash表获取SideTables类型的引用表
oldTable = &SideTables()[oldObj];
} else {
//否则将旧引用表置空
oldTable = nil;
}
if (haveNew) {
//根据haveNew为真,则根据新值的对象在全局的一个hash表获取SideTables类型的引用表
newTable = &SideTables()[newObj];
} else {
//否则将新引用表置空
newTable = nil;
}
//同时对新引用表和旧引用表加锁
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
//如果haveOld为真,而且location指针指向的对象并不是oldObj
if (haveOld && *location != oldObj) {
//那么可能旧值已经更改,解锁两个表,然后重试
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}
// Prevent a deadlock between the weak reference machinery
// and the +initialize machinery by ensuring that no
// weakly-referenced object has an un-+initialized isa.
/**
通过保证没有一个弱引用的对象是没有一个没有initialized的isa
来打断弱引用架构和+initialize架构间的死锁
*/
if (haveNew && newObj) {
//获取新值对象的元类对象
Class cls = newObj->getIsa();
//如果新值的元类对象和之前初始化的不一致,且元类未初始化
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
//解锁两个表
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
//初始化新值对象的元类对象
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));
// If this class is finished with +initialize then we're good.
// If this class is still running +initialize on this thread
// (i.e. +initialize called storeWeak on an instance of itself)
// then we may proceed but it will appear initializing and
// not yet initialized to the check above.
// Instead set previouslyInitializedClass to recognize it on retry.
previouslyInitializedClass = cls;
//跳转到重试
goto retry;
}
}
if (haveOld) {
//在旧值的weak表中清除旧值,如果weak表为空,同时在entry中删除weak表
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
if (haveNew) {
/**
将新值的weak表添加在entry中,并且将新值注册到weak表中
weak_register_no_lock如果返回nil,则表示弱引用存放被拒绝了
1.类中实现了allowsWeakReference,并且返回NO,表示不允许弱引用
2.该对象正在调用deallocating进行释放
*/
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
//同时将是弱引用的标记写在对象的refcount table的表中
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
// Do not set *location anywhere else. That would introduce a race.
//将location对象指针指向新值
*location = (id)newObj;
}
else {
//没有性质,什么都不做
}
//两个表同时解锁
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
//返回新值对象
return (id)newObj;
}
/**
这个函数存放一个新值到一个使用__weak修饰的变量
可以在任何地方指定一个__weak变量
location:弱引用自身的内存地址
newObj:这个弱引用指针需要指向的值
返回: 返回新值
*/
id
objc_storeWeak(id *location, id newObj)
{
return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object *)newObj);
}
通过上面的分析,weak修饰的成员变量的赋值,比strong修饰的成员变量赋值得多了。可以总结一下:
- objc_storeWeak函数不止是用来对对象成员变量的赋值,还包括所有使用__weak修饰符修饰的变量的赋值,以后还需要继续看回这个方法
- 有一个全局的weak表存放在所有指向weak修饰的对象的指针的的指针,这个指针是该对象使用了weak修饰对象的引用的指针
- 在对weak修饰的变量进行赋值的时候,会将旧值的weak表释放掉,并对创建新值的weak表,也就是说,在成员变量中大量使用weak变量的话,会一定程度上占用cpu的资源,会有效率损耗
- 在赋值过成功有加锁和解锁过程,所以整个赋值过程是线程安全的
后记
在了解strong和weak修饰的成员赋值的过程,发现strong修饰的成员变量只是增加所持有对象的循环引用,而weak修饰的成员变量,则相对复杂很多。在了解过程中,可能还是会有理解偏差,需要多次阅读代码再补充。
还有一个就是要尽快弄好一个High Sierra来打断点调试真实的运行过程,现在只是在源代码上面的分析理解。
后记的后记
终于将系统降级到High Sierra了,通过objc的源代码进行调试,证明我的理解并没有太大的差异。
weak修饰的成员变量进行赋值的调用关系如下
strong修饰的成员变量进行赋值的调用关系如下
终于正式了自己测瞎猜,哈哈。开心!