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修飾的成員變量賦值得多了??梢钥偨Y一下:

  • objc_storeWeak函數不止是用來對對象成員變量的賦值,還包括所有使用__weak修飾符修飾的變量的賦值,以后還需要繼續看回這個方法
  • 有一個全局的weak表存放在所有指向weak修飾的對象的指針的的指針,這個指針是該對象使用了weak修飾對象的引用的指針
  • 在對weak修飾的變量進行賦值的時候,會將舊值的weak表釋放掉,并對創建新值的weak表,也就是說,在成員變量中大量使用weak變量的話,會一定程度上占用cpu的資源,會有效率損耗
  • 在賦值過成功有加鎖和解鎖過程,所以整個賦值過程是線程安全的

后記

在了解strong和weak修飾的成員賦值的過程,發現strong修飾的成員變量只是增加所持有對象的循環引用,而weak修飾的成員變量,則相對復雜很多。在了解過程中,可能還是會有理解偏差,需要多次閱讀代碼再補充。

還有一個就是要盡快弄好一個High Sierra來打斷點調試真實的運行過程,現在只是在源代碼上面的分析理解。

后記的后記

終于將系統降級到High Sierra了,通過objc的源代碼進行調試,證明我的理解并沒有太大的差異。

weak修飾的成員變量進行賦值的調用關系如下


weak成員變量的調用棧

strong修飾的成員變量進行賦值的調用關系如下


strong成員變量的調用棧

終于正式了自己測瞎猜,哈哈。開心!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容