iOS深入理解property之strong和weak

前言

在上一篇文章是的时候,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修饰的成员变量进行赋值的调用关系如下


weak成员变量的调用栈

strong修饰的成员变量进行赋值的调用关系如下


strong成员变量的调用栈

终于正式了自己测瞎猜,哈哈。开心!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容