iOS面試關(guān)于屬性copy strong weak assign

copy

copy,引用計(jì)數(shù)會(huì)+1.然而設(shè)置新值并不會(huì)保留舊值,而是將其拷貝。

NSString對(duì)象為什么盡量用copy來修飾?

我們通過代碼查看copy和strong修飾的區(qū)別

#import "ViewController.h"

@interface ViewController ()

// copy字符串
@property (nonatomic, copy) NSString *myCopyStr;
// 強(qiáng)引用str
@property (nonatomic, strong) NSString *strongStr;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
   NSMutableString *testStr = [[NSMutableString alloc] initWithString:@"測(cè)試"];
//    NSString *testStr = @"測(cè)試";
    self.myCopyStr = testStr;
    self.strongStr = testStr;
    
    NSLog(@"testStr : 指針地址 %p", testStr);
    NSLog(@"myCopyStr : 指針地址 %p", _myCopyStr);
    NSLog(@"strongStr : 指針地址 %p", _strongStr);
    
    NSLog(@"----------------------------------------------------");
    [testStr appendString:@"sss"];
    NSLog(@"testStr : 指針地址 %p ,內(nèi)容%@", testStr , testStr);
    NSLog(@"myCopyStr : 指針地址 %p ,內(nèi)容%@", _myCopyStr,_myCopyStr);
    NSLog(@"strongStr : 指針地址 %p ,內(nèi)容%@", _strongStr,_strongStr);
}

打印結(jié)果

2018-07-17 14:52:35.066351+0800 property[51796:5806435] testStr : 指針地址 0x600000253c20
2018-07-17 14:52:35.066535+0800 property[51796:5806435] myCopyStr : 指針地址 0x600000037d40
2018-07-17 14:52:35.066662+0800 property[51796:5806435] strongStr : 指針地址 0x600000253c20
2018-07-17 14:52:35.066793+0800 property[51796:5806435] ----------------------------------------------------
2018-07-17 14:52:35.067159+0800 property[51796:5806435] testStr : 指針地址 0x600000253c20 ,內(nèi)容測(cè)試sss
2018-07-17 14:52:35.067315+0800 property[51796:5806435] myCopyStr : 指針地址 0x600000037d40 ,內(nèi)容測(cè)試
2018-07-17 14:52:35.067464+0800 property[51796:5806435] strongStr : 指針地址 0x600000253c20 ,內(nèi)容測(cè)試sss

結(jié)論:我們可以看出: 通過strong修飾的字符串,strongStr和testStr指向同一塊內(nèi)存地址,testStr修改對(duì)應(yīng)的值,也會(huì)相對(duì)應(yīng)修改了strong修飾的字符串strongStr。而copy是重新拷貝了一份,申請(qǐng)了一塊獨(dú)立的內(nèi)存,無法被影響。

接下來我們看看源碼

id 
object_copy(id oldObj, size_t extraBytes)
{
    return _object_copyFromZone(oldObj, extraBytes, malloc_default_zone());
}

static id 
_object_copyFromZone(id oldObj, size_t extraBytes, void *zone)
{
    if (oldObj->isTaggedPointerOrNil()) return oldObj;

    // fixme this doesn't handle C++ ivars correctly (#4619414)

    Class cls = oldObj->ISA(/*authenticated*/true);
    size_t size;
    id obj = _class_createInstanceFromZone(cls, extraBytes, zone,
                                           OBJECT_CONSTRUCT_NONE, false, &size);
    if (!obj) return nil;

    // Copy everything except the isa, which was already set above.
    uint8_t *copyDst = (uint8_t *)obj + sizeof(Class);
    uint8_t *copySrc = (uint8_t *)oldObj + sizeof(Class);
    size_t copySize = size - sizeof(Class);
    memmove(copyDst, copySrc, copySize); // 拷貝對(duì)象的內(nèi)存數(shù)據(jù)

    fixupCopiedIvars(obj, oldObj); // 處理對(duì)象的ARC 

    return obj;
}

最終調(diào)用的源碼

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}

copy和strong修飾的屬性在底層編譯的不一致,主要還是llvm中對(duì)其進(jìn)行了不同的處理的結(jié)果。copy的賦值是通過objc_setProperty,而strong的賦值時(shí)通過self + 內(nèi)存平移(即將指針通過平移移至name所在的位置,然后賦值),然后還原成 strong類型

strong & copy 在底層調(diào)用objc_storeStrong,本質(zhì)是新值retain,舊值release

strong

strong: 強(qiáng)引用,會(huì)使引用計(jì)數(shù)+1.setter方法賦值時(shí),會(huì)保留新值,并釋放舊值,然后在將新值設(shè)置。

weak

弱引用,引用計(jì)數(shù)不增加。setter方法賦值時(shí),即不保留新值,也不釋放舊值。當(dāng)對(duì)象被銷毀時(shí),屬性值會(huì)自動(dòng)置nil。

assign

用于基本數(shù)據(jù)類型。CGFloat,NSInteger等

unsafe_unretained

作用于OC對(duì)象,引用計(jì)數(shù)不增加。當(dāng)對(duì)象被銷毀時(shí),屬性值不會(huì)清空,正如字面上的意思,不安全。

retain

1.判斷是否為nonpointer ->散列表
2.操作引用計(jì)數(shù)
a: 如果不是 nonpointer -> 散列表
spinlock_t slock; 開解鎖
RefcountMap refcnts; 引用計(jì)數(shù)表
weak_table_t weak_table; 弱應(yīng)用表

b: 是否正在釋放 如果正在釋放就不需要操作引用計(jì)數(shù)了
c: extra_rc + 1 滿了 - 散列表
d: carry 滿 extra_rc 滿/2 -> extra_rc 滿/2 -> 散列表 (開鎖關(guān)鎖)

retain源碼

ALWAYS_INLINE id 
objc_object::rootRetain()
{
    return rootRetain(false, RRVariant::Fast);
}

ALWAYS_INLINE bool 
objc_object::rootTryRetain()
{
    return rootRetain(true, RRVariant::Fast) ? true : false;
}

ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
    if (slowpath(isTaggedPointer())) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    oldisa = LoadExclusive(&isa.bits);

    if (variant == RRVariant::FastOrMsgSend) {
        // These checks are only meaningful for objc_retain()
        // They are here so that we avoid a re-load of the isa.
        if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {
            ClearExclusive(&isa.bits);
            if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {
                return swiftRetain.load(memory_order_relaxed)((id)this);
            }
            return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
        }
    }

    if (slowpath(!oldisa.nonpointer)) {
        // a Class is a Class forever, so we can perform this check once
        // outside of the CAS loop
        if (oldisa.getDecodedClass(false)->isMetaClass()) {
            ClearExclusive(&isa.bits);
            return (id)this;
        }
    }

    do {
        transcribeToSideTable = false;
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain(sideTableLocked);
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        if (slowpath(newisa.isDeallocating())) {
            ClearExclusive(&isa.bits);
            if (sideTableLocked) {
                ASSERT(variant == RRVariant::Full);
                sidetable_unlock();
            }
            if (slowpath(tryRetain)) {
                return nil;
            } else {
                return (id)this;
            }
        }
        uintptr_t carry;
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); 
 // extra_rc++ 
        
      //蘋果為什么要這么設(shè)計(jì)???
    //因?yàn)榇鎯?chǔ)到散列表中需要開鎖解鎖操作,所以這里只放一半并且蘋果也解釋了  slowpath是很小的可能性

// x 很可能不為 0,希望編譯器進(jìn)行優(yōu)化
#define fastpath(x) (__builtin_expect(bool(x), 1))
// x 很可能為 0,希望編譯器進(jìn)行優(yōu)化
#define slowpath(x) (__builtin_expect(bool(x), 0))

        if (slowpath(carry)) { //操作散列表 extra_rc  如果滿了 放一半到散列表里面 newisa.extra_rc = RC_HALF;
            // newisa.extra_rc++ overflowed
            if (variant != RRVariant::Full) {
                ClearExclusive(&isa.bits);
                return rootRetain_overflow(tryRetain);
            }
            // Leave half of the retain counts inline and 
            // prepare to copy the other half to the side table.
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;
            newisa.extra_rc = RC_HALF;
            newisa.has_sidetable_rc = true;
        }
    } while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits)));

    if (variant == RRVariant::Full) {
        if (slowpath(transcribeToSideTable)) {
            // Copy the other half of the retain counts to the side table.
            sidetable_addExtraRC_nolock(RC_HALF);
        }

        if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    } else {
        ASSERT(!transcribeToSideTable);
        ASSERT(!sideTableLocked);
    }

    return (id)this;
}

NONPOINTER_ISA

蘋果將 isa 設(shè)計(jì)成了聯(lián)合體,在 isa 中存儲(chǔ)了與該對(duì)象相關(guān)的一些內(nèi)存的信息,原因也如上面所說,并不需要 64 個(gè)二進(jìn)制位全部都用來存儲(chǔ)指針。

來看一下 isa 的結(jié)構(gòu):


// x86_64 架構(gòu)
struct {
    uintptr_t nonpointer        : 1;  // 0:普通指針,1:優(yōu)化過,使用位域存儲(chǔ)更多信息
    uintptr_t has_assoc         : 1;  // 對(duì)象是否含有或曾經(jīng)含有關(guān)聯(lián)引用
    uintptr_t has_cxx_dtor      : 1;  // 表示是否有C++析構(gòu)函數(shù)或OC的dealloc
    uintptr_t shiftcls          : 44; // 存放著 Class、Meta-Class 對(duì)象的內(nèi)存地址信息
    uintptr_t magic             : 6;  // 用于在調(diào)試時(shí)分辨對(duì)象是否未完成初始化
    uintptr_t weakly_referenced : 1;  // 是否被弱引用指向
    uintptr_t deallocating      : 1;  // 對(duì)象是否正在釋放
    uintptr_t has_sidetable_rc  : 1;  // 是否需要使用 sidetable 來存儲(chǔ)引用計(jì)數(shù)
    uintptr_t extra_rc          : 8;  // 引用計(jì)數(shù)能夠用 8 個(gè)二進(jìn)制位存儲(chǔ)時(shí),直接存儲(chǔ)在這里
};
 
// arm64 架構(gòu)
struct {
    uintptr_t nonpointer        : 1;  // 0:普通指針,1:優(yōu)化過,使用位域存儲(chǔ)更多信息
    uintptr_t has_assoc         : 1;  // 對(duì)象是否含有或曾經(jīng)含有關(guān)聯(lián)引用
    uintptr_t has_cxx_dtor      : 1;  // 表示是否有C++析構(gòu)函數(shù)或OC的dealloc
    uintptr_t shiftcls          : 33; // 存放著 Class、Meta-Class 對(duì)象的內(nèi)存地址信息
    uintptr_t magic             : 6;  // 用于在調(diào)試時(shí)分辨對(duì)象是否未完成初始化
    uintptr_t weakly_referenced : 1;  // 是否被弱引用指向
    uintptr_t deallocating      : 1;  // 對(duì)象是否正在釋放
    uintptr_t has_sidetable_rc  : 1;  // 是否需要使用 sidetable 來存儲(chǔ)引用計(jì)數(shù)
    uintptr_t extra_rc          : 19;  // 引用計(jì)數(shù)能夠用 19 個(gè)二進(jìn)制位存儲(chǔ)時(shí),直接存儲(chǔ)在這里
}

注意這里的 has_sidetable_rc 和 extra_rc,has_sidetable_rc 表明該指針是否引用了 sidetable 散列表,之所以有這個(gè)選項(xiàng),是因?yàn)樯倭康囊糜?jì)數(shù)是不會(huì)直接存放在 SideTables 表中的,對(duì)象的引用計(jì)數(shù)會(huì)先存放在 extra_rc 中,當(dāng)其被存滿時(shí),才會(huì)存入相應(yīng)的 SideTables 散列表中,SideTables 中有很多張 SideTable,每個(gè) SideTable 也都是一個(gè)散列表,而引用計(jì)數(shù)表就包含在 SideTable 之中。

SideTables

引用計(jì)數(shù)要么存放在 isa 的 extra_rc 中,要么存放在引用計(jì)數(shù)表中,而引用計(jì)數(shù)表包含在一個(gè)叫 SideTable 的結(jié)構(gòu)中,它是一個(gè)散列表,也就是哈希表。而 SideTable 又包含在一個(gè)全局的 StripeMap 的哈希映射表中,這個(gè)表的名字叫 SideTables。

散列表(Hash table,也叫哈希表),是根據(jù)建(Key)而直接訪問在內(nèi)存存儲(chǔ)位置的數(shù)據(jù)結(jié)構(gòu)。也就是說,它通過一個(gè)關(guān)于鍵值得函數(shù),將所需查詢的數(shù)據(jù)映射到表中一個(gè)位置來訪問記錄,這加快了查找速度。這個(gè)映射函數(shù)稱作散列函數(shù),存放記錄的數(shù)組稱作散列表

// SideTables
static StripedMap<SideTable>& SideTables() {
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
 
// SideTable
struct SideTable {
    spinlock_t slock;           // 自旋鎖
    RefcountMap refcnts;        // 引用計(jì)數(shù)表
    weak_table_t weak_table;    // 弱引用表
    
    // other code ...
};

weak_table_t weak_table; 弱應(yīng)用表
散列表 在內(nèi)存里面有多張 + 最多能夠多少張???
回答:
一個(gè) SideTables 包含眾多 SideTable,每個(gè) SideTable 中又包含了三個(gè)元素,spinlock_t 自旋鎖、RefcountMap 引用計(jì)數(shù)表、weak_table_t 弱引用表。所以既然 SideTables 是一個(gè)哈希映射的表,為什么不用 SideTables 直接包含自旋鎖,引用計(jì)數(shù)表和弱引用表呢?這是因?yàn)樵诒姸嗑€程同時(shí)訪問這個(gè) SideTable 表的時(shí)候,為了保證數(shù)據(jù)安全,需要給其加上自旋鎖,如果只有一張 SideTable 的表,那么所有數(shù)據(jù)訪問都會(huì)出一個(gè)進(jìn)一個(gè),單線程進(jìn)行,非常影響效率,雖然自旋鎖已經(jīng)是效率非常高的鎖,這會(huì)帶來非常不好的用戶體驗(yàn)。針對(duì)這種情況,將一張 SideTable 分為多張表的 SideTables,再各自加鎖保證數(shù)據(jù)的安全,這樣就增加了并發(fā)量,提高了數(shù)據(jù)訪問的效率,這就是為什么一個(gè) SideTables 下涵蓋眾多 SideTable 表的原因。

retain總結(jié)

首先判斷是否非nonpointer指針,直接操作散列表,進(jìn)行引用計(jì)數(shù)+1的操作。
如果是nonpointer,操作extra_rc進(jìn)行常規(guī)的引用計(jì)數(shù)+1操作,當(dāng)然這里還有一個(gè)判斷。如果屬性值正在進(jìn)行釋放,也不需要進(jìn)行引用計(jì)數(shù)的操作,同時(shí)這里還有一個(gè)細(xì)節(jié)要注意,蘋果設(shè)計(jì)了一個(gè)算法是比較到位的,在真機(jī)上只有8位,如果說8位滿了,需要額外借助散列表來存儲(chǔ),它會(huì)把extra_rc滿狀態(tài)的一半存儲(chǔ)2的7次方到散列表中,剩下的2的7次方還是存在nonpointer的extra_rc中,來進(jìn)行正常的引用計(jì)數(shù)操作。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容